• 목표

    팀 프로젝트에서 여러곳에 사용하는 infinityQuery를 템플릿으로 작성하여, 변수만 입력하면 추가 코드작성 없이 사용할 수 있는 코드를 작성하려 했습니다.

  • 문제

    타입스크립트에서 리턴값을 추론하는데 실패하여 오류가 발생하거나, 실행은 되지만 리턴값의 타입이 InfiniteData가 아닌 상황이 발생했습니다.

  • 해결 과정

    1: 이전에 작성된 infiniteQuery는 리턴값이 InfiniteData<데이터 타입 | null> 로 리턴되었습니다. 그러나 다른 팀원분들도 사용할 수 있도록 option으로 지정된 상수값을 변수로 전환하자 문제가 발생했습니다.

    2: No overload matches this call. 제가 입력한 변수값들을 받아줄 함수가 없다는 에러였습니다.

    에러 코드를 살펴보니 UseInfiniteQueryOptions이라는 단어가 나와서 tanstackQuery의 공식 홈페이지에서 검색을 해봤지만 아무런 설명도 없었습니다.

    3: 잠시 마음을 가라앉히고 자리에 다시 앉아 UseInfiniteQueryOptions이 어떻게 정의되어있는지 확인했습니다.

    interface UseInfiniteQueryOptions<TQueryFnData = unknown, TError = DefaultError, TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, TPageParam = unknown> extends OmitKeyof<InfiniteQueryObserverOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam>, 'suspense'> {
        /**
         * Set this to `false` to unsubscribe this observer from updates to the query cache.
         * Defaults to `true`.
         */
        subscribed?: boolean;
    }
    

    types.d.ts 라는 파일에 이런 인터페이스 정의가 있었고, 이대로 따르기로 했습니다.

    4: 그렇게 완성된 코드는 다음과 같았습니다.

    options?: UseInfiniteQueryOptions<
        T,
        Error,
        T,
        [string],
        number | null
      >
    

    그러나 이번에는 실행된 결과값의 타입이 엉뚱한 T 로 나오는 상황이 발생했습니다. 알아서 InfiniteData<데이터 타입 , number | null> 으로 변환될것이라 생각했던 저는 왜 이런지 원인을 몰랐습니다.

    디폴트값이 TData = TQueryFnData 인데 InfiniteData<데이터 타입 | null> 이 리턴되던 함수였으니 당연히 변환해주는것 아닌가? 라는 생각에 작성했던 코드였는데 전혀 예상치 못한 상황이 발생하자 많이 당황스러웠습니다. …options 때문에 타입스크립트가 타입 추론에 실패한건가 라는 생각도 했었습니다.

    5: 함수의 입력값들 타입을 고정하면 될까? 내부에서 자동으로 변환해주잖아. 라는 생각으로 return useInfiniteQuery를 return useInfiniteQuery<T, Error, T, [string], number | null>로도 바꿔보았습니다. 에러는 나진 않았지만 여전히 T 가 리턴됐습니다.

    6: 결국 원인을 찾지 못하고 강제로 타입을 변환해서 임시로 조치한 후 useInfiniteQuery 로 계속 검색하다가 세번째 인자 T를 아예 InfiniteData로 지정한 코드들을 봤습니다. TData = TQueryFnData 코드를 보고 잘못된 선입견을 가진것이었습니다. 바로 임시 조치한 타입 변환을 해제하고 코드를 수정하니 정상 동작했습니다.

  • 원인

    아래는 useInfiniteQuery 가 정의되어있는 코드입니다

    import { DefaultError, InfiniteData, QueryKey, QueryClient } from '@tanstack/query-core';
    import { DefinedUseInfiniteQueryResult, UseInfiniteQueryResult, UseInfiniteQueryOptions } from './types.js';
    import { DefinedInitialDataInfiniteOptions, UndefinedInitialDataInfiniteOptions } from './infiniteQueryOptions.js';
    
    declare function useInfiniteQuery<TQueryFnData, TError = DefaultError, TData = InfiniteData<TQueryFnData>, TQueryKey extends QueryKey = QueryKey, TPageParam = unknown>(options: DefinedInitialDataInfiniteOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam>, queryClient?: QueryClient): DefinedUseInfiniteQueryResult<TData, TError>;
    declare function useInfiniteQuery<TQueryFnData, TError = DefaultError, TData = InfiniteData<TQueryFnData>, TQueryKey extends QueryKey = QueryKey, TPageParam = unknown>(options: UndefinedInitialDataInfiniteOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam>, queryClient?: QueryClient): UseInfiniteQueryResult<TData, TError>;
    declare function useInfiniteQuery<TQueryFnData, TError = DefaultError, TData = InfiniteData<TQueryFnData>, TQueryKey extends QueryKey = QueryKey, TPageParam = unknown>(options: UseInfiniteQueryOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam>, queryClient?: QueryClient): UseInfiniteQueryResult<TData, TError>;
    
    export { useInfiniteQuery };
    

    TData = InfiniteData<TQueryFnData>로 정의되어있었고, 이는 TData를 정의하지 않으면 디폴트로 InfiniteData<TQueryFnData>를 사용한다는 의미였습니다.

    options 와 이전 코드들의 동작으로 잘못된 선입견이 만들어져 헤메게 되었습니다.

  • 해결방법

    options?: UseInfiniteQueryOptions<
        T,
        Error,
        InfiniteData<T, number | null>,
        [string],
        number | null
      >
    
      return useInfiniteQuery<
        T,
        Error,
        InfiniteData<T, number | null>,
        [string],
        number | null
      >
    

    정상적으로 InfiniteData<T, number | null> 를 리턴하도록 타입을 지정하여 문제를 해결했습니다.

  • 후기

    비슷한 문제가 생겼을때, 어떤 자료들을 먼저 찾아서 생각을 정리해야 하는지 알 수 있었습니다.

    다음에 비슷한 문제가 생겼을때 이 경험이 큰 도움이 되리라고 생각합니다.