useSuspenseQuery 톺아보기(2) - useBaseQuery

김준하·2025년 5월 21일

useSuspenseQuery

목록 보기
2/2
post-thumbnail

useBaseQuery.ts

모든 코드를 다 살펴보기 보다는, 필요하거나 중요하다고 생각되는 부분만 발췌해서 살펴보려고 한다.

useBaseQuery.ts

queryFn 유효성 확인

production이 아닌 환경에서는 queryFn이 유효한지, 즉 falsy한 값인지를 확인하고 falsy한 값이라면 콘솔에 에러를 출력한다.

TanStack Query에서는 쿼리를 실행하고 싶지 않다면 queryFn으로 skipToken을 전달하라고 하고 있다.

  if (process.env.NODE_ENV !== 'production') {
    if (!defaultedOptions.queryFn) {
      console.error(
        `[${defaultedOptions.queryHash}]: No queryFn was passed as an option, and no default queryFn was found. The queryFn parameter is only optional when using a default queryFn. More info here: https://tanstack.com/query/latest/docs/framework/react/guides/default-query-function`,
      )
    }
  }

기본 staleTime, gcTime 설정

ensureSuspenseTimers 라는 함수를 통해 기본 staleTime과 gcTime을 설정해준다.

ensureSuspenseTimers(defaultedOptions)
export const ensureSuspenseTimers = (
  defaultedOptions: DefaultedQueryObserverOptions<any, any, any, any, any>,
) => {
  const originalStaleTime = defaultedOptions.staleTime

  if (defaultedOptions.suspense) {
    // Handle staleTime to ensure minimum 1000ms in Suspense mode
    // This prevents unnecessary refetching when components remount after suspending
    defaultedOptions.staleTime =
      typeof originalStaleTime === 'function'
        ? (...args) => Math.max(originalStaleTime(...args), 1000)
        : Math.max(originalStaleTime ?? 1000, 1000)

    if (typeof defaultedOptions.gcTime === 'number') {
      defaultedOptions.gcTime = Math.max(defaultedOptions.gcTime, 1000)
    }
  }
}

코드를 확인해보면 위 함수는 suspense 옵션을 주었을 때만 동작하게 되어있다.

또한 staleTime와 gcTime을 기본 최소값으로 1000ms(1초)를 설정한다.

만약 staleTime과 gcTime에 값을 넘겨준다면 넘겨준 값과 1000ms를 비교하여 더욱 큰 값을 적용한다.

Handle staleTime to ensure minimum 1000ms in Suspense mode
This prevents unnecessary refetching when components remount after suspending

TanStack Query의 주석에서는 위와 같이 설명하고 있다.

의도를 파악해보자면, 아마도 useQueries를 사용할 때 suspense를 적용하거나 useSuspenseQueries를 사용할 때 발생하는 문제를 막기 위함일 것으로 예상된다.

useSuspenseQueries를 기준으로 설명한다면, 만약 a 쿼리가 완료되었지만 b 쿼리가 완료되지 않았다면 useSuspenseQueries는 여전히 suspend 상태이다. 그 상태에서 b 쿼리가 완료되었다면 useSuspenseQueries의 결과가 화면에 나타나야 할 것인데, 이 때 staleTime을 0으로 설정했다면 a 쿼리는 unstable한 상태일 것이고 refetch를 시도할 것이다.

따라서 이러한 문제를 막을 수 있는 최소한의 시간을 TanStack Query에서는 1000ms(1초)라고 판단한 것으로 예상된다. (참고 링크)

fetchOptimistic 호출

shouldSuspend라는 함수를 통해 fetchOptimistic을 호출할 것인지 결정한다.

suspense 옵션이 true이고 쿼리 결과가 pending 상태이면 fetchOptimistic을 throw한다.

useSuspenseQuery에서는 suspense 옵션을 항상 true로 전달하고 있기 때문에 쿼리 결과가 pending 상태라면 항상 fetchOptimistic을 throw한다.

즉, pending 상태라면 Promise를 throw하게 되는 것이고, 이것 덕분에 React의 Suspense와 함께 사용한다면 결과가 pending 상태일 때 Suspense의 fallback이 나타날 수 있는 것이다.(내부적으로 Suspense는 Promise를 catch하게 되어 있다.)

이 때 쿼리 옵션과 observer를 전달한다.

// Handle suspense
if (shouldSuspend(defaultedOptions, result)) {
  throw fetchOptimistic(defaultedOptions, observer, errorResetBoundary)
}
export const shouldSuspend = (
  defaultedOptions:
    | DefaultedQueryObserverOptions<any, any, any, any, any>
    | undefined,
  result: QueryObserverResult<any, any>,
) => defaultedOptions?.suspense && result.isPending

생각보다 useSuspenseQuery를 위한 코드는 많지 않았다.

몇 가지 옵션을 기본으로 설정해주고, pending 상태라면 Promise를 throw 하게끔 설계되었다는 점 빼고는 useQuery와 크게 다르지 않아 보인다.

나중에 기회가 된다면 queryObserver가 역할을 어떻게 수행하는지도 좀 더 자세하게 알아보고 싶다.

2개의 댓글

comment-user-thumbnail
2025년 6월 1일

맨날 간단하게 조사하고 작성했다고 겸손 떠시는데 조금이라도 차근차근 맨날맨날 꾸준히 하는 게 진짜 기특하고 대단한 것 같아여!! 프론트엔드 지식에 낯설어서 막 어어어엄청 잘 이해하지는 못하겠지만 그래도 항상 자극받고 갑니다 ^~^

1개의 답글