[TypeScript] query의 리턴값 중 data의 type이 undefined일 수 있지만 잡히지 않을 때 (strictNullChecks)

date
May 18, 2023
slug
typescript-query의-리턴값-중-data의-type이-undefined일-수-있지만-잡히지-않을-때
category
Dev
status
Public
tags
Next.js
GraphQL
Apollo
Front-end
Back-end
React
Node.js
keywords
summary
apollo client의 useQuery hook을 사용하는데 data가 undefined일 수 있다고 했지만 잡히지 않아요
type
Post
Last updated
May 18, 2023 07:47 AM
Created time
May 18, 2023 07:26 AM

무슨 일인가?

이번에 리뉴얼 된 코드를 베이스로 사내 프로젝트의 신규 기능을 구현하고자 결정했다.
코드 베이스를 바꿔서 우리 프로젝트에 맞게 수정하며 테스트용 aws 빈스톡에 배포하고 있는데.
codebuild도 잘 돌아서 deploy까지 배포는 잘 되었는데, 접근해보니 internal server error가 발생한 것.
notion image
 

원인을 찾아보자

원인이 무엇일까 하고 빈스톡의 로그를 확인해보니, 빌드시 타입레벨에서 잡히지 않은 undefined 참조 에러가 발생한 것.
notion image
이래서 타입스크립트를 잘 사용해야 배포하기도 전에 에러를 잡을 수 있는데..
notion image
밑줄 친 부분이 에러의 원인이었다.
 

아니 근데 왜 타입에서 못잡았지?

생각해보니, 원래 react-query나 apollo client의 useQuery hook에선 로딩중엔 당연히 data가 없으니
data에 대해서 undefined일 수 있어서 타입에도 정의가 되어 있는 걸로 알고 있다.
그런데 왜 type레벨에서 잡지 못한걸까?
// 문제의 쿼리
const { data, loading } = useGqlCurrentUserQuery({
  ssr: true,
  ...(RUNTIME_IS_BROWSER && {
    fetchPolicy: authCookie ? 'cache-and-network' : 'cache-only',
  }),
})

// useGqlCurrentUserQuery의 선언부
export function useGqlCurrentUserQuery(baseOptions?: Apollo.QueryHookOptions<Types.GqlCurrentUserQuery, Types.GqlCurrentUserQueryVariables>) {
  const options = {...defaultOptions, ...baseOptions}
  return Apollo.useQuery<Types.GqlCurrentUserQuery, Types.GqlCurrentUserQueryVariables>(GqlCurrentUserDocument, options);
}

// Apollo.useQuery의 선언부
export declare function useQuery<TData = any, TVariables = OperationVariables>(query: DocumentNode | TypedDocumentNode<TData, TVariables>, options?: QueryHookOptions<TData, TVariables>): QueryResult<TData, TVariables>;

// Apollo의 QueryResult<TData, TVariables> 선언부
export interface QueryResult<TData = any, TVariables = OperationVariables> extends ObservableQueryFields<TData, TVariables> {
  client: ApolloClient<any>;
  observable: ObservableQuery<TData, TVariables>;
  data: TData | undefined; // <----------- 예상대로 잘 undefined가 있다.
  previousData?: TData;
  error?: ApolloError;
  loading: boolean;
  networkStatus: NetworkStatus;
  called: boolean;
}
위 코드에서 보듯이, 예상대로 Apollo에선 정상적으로 타입을 선언해주었다.
 

삽질의 시작

왜 못잡지? 의문을 가지며 여러가지 경우의 수를 생각하며 원인을 쥐잡듯이 찾기 시작했다
  • 내 vscode가 문제인가? - NO
  • 레거시 프로젝트만 보다가 와서 최신 typescript의 breaking change인가? - NO
  • 최근 apollo client의 react-hook에 대한 타입 변경점인가? - NO
  • 내가 모르던 typescript의 추론 방식이 있나? - NO
찾다 찾다 도저히 알 수가 없어서, 최근 함께 스터디중인 분들께도 도움을 요청했으나..
notion image
 

그래서 어떻게 해결했나? 기본에 충실하자 🙂

notion image
하하 혹시나 했는데 역시나..
완전 배제하고 있던 시작점인 tsconfig.json에서 원인을 찾을 수 있었다.
typescript를 중요시 하는 나로선 당연히 tsconfig의 strict관련 옵션들이 활성화 되어 있을 것이라고 생각했다.
하지만, 변경되는 코드베이스에서는 tsconfig의 설정을 보니 strict는 물론 관련 옵션도 false로 되어 있었다.
원인은 바로 tsconfig의 strictNullChecks가 꺼져있었기 때문에 strict하게 null과 undefined를 체크하고 있지 않아서 였던^^
다음부턴 시작점부터.. 차근차근 디버깅 해봐야겠다 하하