
TanStack Query의 useSuspenseQuery는 useQuery와 다르게 결과를 가지고 있음을 항상 보장한다. 또한 React의 Suspense와 함께 사용하면 Streaming 기능도 이용할 수 있다.
이러한 동작 원리가 신기하고 궁금해서 소스코드를 살펴보면서 천천히 알아보려 한다.
production 환경이 아니라면 가장 먼저 queryFn으로 skipToken이 전달되었는지 확인한다. (링크)
왜냐하면 useSuspenseQuery는 항상 쿼리 결과가 존재하는 것을 보장하기 때문이다. 그리고 쿼리 결과가 존재하려면 queryFn이 반드시 실행되어야 한다.
운영 환경에서는 굳이 skipToken 전달 여부를 확인하지 않는 것은, 쿼리 결과가 존재하지 않더라도 서비스에 영향을 미치지 않는 경우도 있을 수 있다고 TanStack Query에서 판단한 듯 하다.
if (process.env.NODE_ENV !== 'production') {
if ((options.queryFn as any) === skipToken) {
console.error('skipToken is not allowed for useSuspenseQuery')
}
}
모든 쿼리들을 확인하지는 못했지만, TanStack Query의 대부분은 useBaseQuery를 사용한다.
useSuspenseQuery도 마찬가지로 useBaseQuery를 호출하고 있다.
단, 몇 가지 속성들은 직접 전달하고 있다.
return useBaseQuery(
{
...options,
enabled: true,
suspense: true,
throwOnError: defaultThrowOnError,
placeholderData: undefined,
},
QueryObserver,
queryClient,
) as UseSuspenseQueryResult<TData, TError>
useSuspenseQuery는 결과를 보장하기 때문에, 항상 쿼리를 실행하도록 설정한다.
suspense 옵션을 전달한다. 이건 useBaseQuery를 살펴볼 때 더 자세히 보자.
쿼리 캐시의 데이터가 undefined일 때만 에러를 throw하도록 설정한다.
쿼리에서 오류가 발생할 때 항상 에러를 던지는 것이 아니다. 지금 쿼리가 실패했더라도 이전에 쿼리가 성공한 적이 있고 그 결과가 캐시에 남아있다면 에러 화면이 아니라 캐시 결과를 가져와서 사용한다. (참고 링크)
export const defaultThrowOnError = <
TQueryFnData = unknown,
TError = DefaultError,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
>(
_error: TError,
query: Query<TQueryFnData, TError, TData, TQueryKey>,
) => query.state.data === undefined
placeholderData는 쿼리가 실행되는 동안 화면에 보여줄 대체 데이터이다. 쿼리 캐시에 유지되지 않는다.
useSuspenseQuery가 쿼리를 싱행하는 동안 suspend 상태가 되고, 어차피 이 때 placeholderData를 사용할 일이 없기 때문에 undefined로 설정한 것이 아닌가 싶다.
export interface UseSuspenseQueryOptions<
TQueryFnData = unknown,
TError = DefaultError,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
> extends OmitKeyof<
UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
'queryFn' | 'enabled' | 'throwOnError' | 'placeholderData'
> {
queryFn?: Exclude<
UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>['queryFn'],
SkipToken
>
}
export interface UseQueryOptions<
TQueryFnData = unknown,
TError = DefaultError,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
> extends OmitKeyof<
UseBaseQueryOptions<TQueryFnData, TError, TData, TQueryFnData, TQueryKey>,
'suspense'
> {}
UseSuspenseQueryOptions는 기본적으로 suspense 옵션이 빠져있는 UseQueryOptions를 사용하고, UseQueryOptions에서 'queryFn' | 'enabled' | 'throwOnError' | 'placeholderData' 가 빠진다.
위에서 확인했듯이 'suspense' | 'enabled' | 'throwOnError' | 'placeholderData'에 기본값을 주고 있기 때문이다.
그리고 기본 queryFn에서 SkipToken 타입을 제거한 것을 사용하는데, useSuspenseQuery 가장 처음에 queryFn이 skipToken인지 확인하는 것과 같이 useSuspenseQuery의 쿼리는 항상 실행되어야 하기 때문이다.
다음 글에는 useSuspenseQuery에서 사용하는 useBaseQuery를 확인해보겠다.
갑자기 이거 읽다가 궁금해서 프론트 쿼리에 대해 글 두세 개 정도 찾아봤어여 그만큼 흥미를 당기는 글이다~~ 이거죠 ^.^ 오랜만에 개발 글 읽으니까 재미있기도 한 것 같구,, 그런데 정말 회사 다니면서 어떻게 이렇게 글도 쓰시는 거죠????? 몸이 몇 개신가요!!