[Open Source] react-query의 useQuery는 어떻게 동작할까? (1)-타입

hellollolo·2024년 7월 9일

서문

우선 react-query는 Tanstack-query로 명칭이 변경되었고 이유는 react 뿐만아니라 여러가지 프레임워크를 대응하기 위해 더 넓은 이름을 사용한 것 같습니다.


실제로 Tanstack 홈페이지를 보면 여러가지 프로젝트를 준비하고 있는 것을 보실 수 있습니다. 이 중에서도 오늘 설명할 것은 가장 유명한 Tanstack-query입니다.

전체 구조

Tanstack-query의 깃허브 저장소를 가서 package.json을 보면 패키지 매니저는 pnpm을 사용하는 것을 알 수 있습니다. 수십개의 패키지를 하나의 저장소에서 관리하는 모노레포의 형태를 하고 있습니다.

아무래도 npm으로는 수십개의 패키지를 관리하기에는 성능이 떨어져 pnpm을 사용하는 것 같습니다. 그리고 명령어 중에 nx라는 키워드도 보이는 데 이는 모노레포는 사용할 때 사용되는 라이브러리입니다.

최상단에 있는 packages에 query 프로젝트에서 만든 수 십개의 프로젝트들이 관리되고 있으며 이 중 react-query, 그 안에서도 가장 자주 쓰이는 useQuery가 오늘 알아 볼 기능입니다.

useQuery

useQuery 파일 자체는 생각보다 길지 않습니다. 대부분의 로직들은 다른 파일에서 수행되고 useQuery 파일에는 타입정도만이 작성되어 있습니다.

아무래도 실제 기능 구현은 useBaseQuery라는 함수에서 실행되는 것 같습니다. 오늘은 타입만을 알아볼 예정이기 때문에 각 타입을 구체적으로 살펴보겠습니다.

useQuery Type

타입스크립트의 함수 오버로딩 기능을 사용하고 있으며 크게 3가지의 타입으로 나뉩니다.

첫 번째 타입

export function useQuery<
  TQueryFnData = unknown,
  TError = DefaultError,
  TData = TQueryFnData,
  TQueryKey extends QueryKey = QueryKey,
>(
  options: DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>,
  queryClient?: QueryClient,
): DefinedUseQueryResult<TData, TError>

두 번째 타입

export function useQuery<
  TQueryFnData = unknown,
  TError = DefaultError,
  TData = TQueryFnData,
  TQueryKey extends QueryKey = QueryKey,
>(
  options: UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>,
  queryClient?: QueryClient,
): UseQueryResult<TData, TError>

세 번째 타입

export function useQuery<
  TQueryFnData = unknown,
  TError = DefaultError,
  TData = TQueryFnData,
  TQueryKey extends QueryKey = QueryKey,
>(
  options: UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
  queryClient?: QueryClient,
): UseQueryResult<TData, TError>

보시면 아시겠지만 useQuery의 제너릭 부분은 3개 모두 동일합니다. 4개의 타입을 받아 다시 options의 제너릭에 넣어주는 형태입니다.

먼저 첫 번째 타입의 options의 인자인
DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>
이 어떻게 구성되어 있는지 차례대로 따라가 보겠습니다.

첫 번째 options 타입 DefinedInitialDataOptions

전달 받은 4개의 인자를 그대로 UseQueryOptions에 넣는 것을 알 수 있습니다. 그리고 initialData라는 key를 하나 추가합니다. 초기 데이터를 넣기 위한 타입으로 보입니다. UseQueryOptions를 따라가 보겠습니다.

OmitKeyOf라는 유틸 타입이 보이는 데 이는 Omit을 react-query에서 살짝 변형한 함수로 세 번째 인자로 option을 넣어줄 수 있는 기능이 있고 대부분은 Omit함수와 기능이 같습니다.

UseQueryOptions는 UseBaseQueryOptions에서 suspense를 제외한 타입을 extends 한다는 것을 알 수 있습니다.

같은 파일의 바로 위에 UseBaseQueryOptions은 QueryObserverOptions를 extends하고 있습니다. 이제 QueryObserverOptions이 어떻게 되어있는지 보겠습니다.

타입이 워낙 길어서 한 페이지에는 다 담기지 않았습니다. 이 타입은 query-core 패키지에 있는 타입인데 여기에 모든 패키지(react-query, vue-query, angular-query 등)에서 쓰이는 핵심 타입들이 담겨있는 듯 합니다. QueryOption를 extends해서 staleTime이나 refetchInterval 등의 옵션들을 추가해주고 있습니다.

WidthRequired는 필수 key를 지정하는 유틸 타입으로 querKey는 key로 필수임을 지정해주고 있습니다. 이제 QueryOptions를 따라가보겠습니다.

이제 더 이상 extends를 하는 타입을 없습니다. 이제 이 QueryOptions로 부터 시작되어 여러가지 타입을 거처 useQuery의 option의 타입까지 오게 되는 된다는 것을 알 수 있습니다.

두 번째 options 타입 UndefinedInitialDataOptions

첫 번째와 거의 유사한 타입입니다.

차이점은 initialData가 undefiend로 들어가는 점 정도입니다. API 함수의 반환 타입이 있냐 없냐에 따라 달라지는 것으로 보입니다.

세 번째 options 타입 UseQueryOptions

UseQueryOptions는 UseBaseQueryOptions에서 suspense를 제외한 타입을 extends합니다.

UseBaseQueryOptions은 바로 QueryObserverOptions를 extends한다. QueryObserverOptions는 react-query 패키지가 아닌 query-core 패키지의 types 파일에 들어있습니다.

QueryObserverOptions 타입는 위에 있는 "첫 번째 타입"에서 다룬 마지막 타입입니다.

두 가지 반환 타입 DefinedUseQueryResult vs UseQueryResult

반환 타입은 DefinedUseQueryResult과 UseQueryResult의 2가지 타입으로 이루어져 있습니다. 먼저 첫 번째를 보겠습니다.

DefinedUseQueryResult(react-query) extends
DefinedQueryObserverResult(query-core) extends
DefinedQueryObserverResult(query-core) =
QueryObserverRefetchErrorResult(query-core) | QueryObserverSuccessResult(query-core) extends
QueryObserverBaseResult(query-core)
의 순서인데 QueryObserverBaseResult 타입을 아래와 같습니다. QueryObserverBaseResult 타입이 return 값의 가장 기본이 되는 속성들이 모여있는 타입으로 보입니다.

두 번째는 UseQueryResult 타입입니다. 타입을 차례대로 따라가보면
UseQueryResult(react-query) =
UseBaseQueryResult(react-query) =
QueryObserverResult(query-core) =
DefinedQueryObserverResult(query-core)
| QueryObserverLoadingErrorResult(query-core)
| QueryObserverLoadingResult(query-core)
| QueryObserverPendingResult(query-core)

의 순서이고 마지막 4개의 타입은 모두 QueryObserverBaseResult(query-core) 타입을 extends하고 있습니다.

이상 useQuery의 타입들을 알아보았습니다. 다음 글은 실 구현부에 대해 알아보겠습니다.

0개의 댓글