React Query를 이용해 Caching 기능을 구현 할 수 있다. 캐싱이란, API 호출결과를 저장해 필요할때 이를 재사용하는 기법이다.
React Query에서 캐싱기능을 제대로 사용하려면 query의 상태는 어떤게 있는지, useQuery가 어떻게 동작하는지, option들은 무엇이 있는지 알아야 한다.
- stale : "상한" 이라는 뜻으로 fresh한 상태와 반대이다. query가 stale한 상태에 있다면 react query는 캐싱된 데이터를 사용하지 않는다.
- fresh : 신선한 즉, 쿼리의 상태가 상하지 않았다는 의미이다. fresh한 상태라면 react query는 캐싱된 데이터를 사용한다.
- fetching : 현재 fetching이 일어나는 쿼리들의 상태이다.
- inactive : 사용되지 않는 쿼리들의 상태이다.
React Query에서는 staleTime 과 cacheTime을 통해 캐싱을 구현한다.
staleTime 이란 query가 stale -> fresh 한 상태로 전환되는데 걸리는 시간이다.
cacheTime 이란 캐시에서 데이터가 제거되는데 까지 걸리는 시간이다. cacheTime이 지난 데이터는 가비지컬렉터에 의해 캐시에서 제거된다.
react Query에서 staleTime의 default값은 0이고, cacheTime의 default는 5분이다. 즉, useQuery를 통해 fetching된 정보는 cache에 저장되는데, 아무런 설정을 해주지 않았다면 stale하다고 여겨지는 것이다.
queryClient의 default option을 지정해주거나, useQuery에 option으로 staleTime을 지정해줌으로써 캐싱을 구현 할 수 있다.
캐싱과정은 아래 블로그를 참고하여 직접 소스코드를 확인해보았다.
먼저 React Query를 사용하려면 APP을 QueryClientProvider를 통해 감싼 후 queryClient를 생성해주어야 한다.
QueryClientProvider는 React Context를 이용해 전역으로 query를 사용할 수 있도록 해준다. client라는 prop을 넘겨받는데 여기 queryClient를 생성해서 제공해 주어야 한다.
여기서 getQueryClientContext 함수는 context가 있다면 context를 return 해준다. contextSharing은 공식문서에 따르면 더이상 사용되지 않는다고 나와있다.
//QueryClientProvider.tsx
export const QueryClientProvider = ({
client,
children,
context,
contextSharing = false,
}: QueryClientProviderProps): JSX.Element => {
React.useEffect(() => {
client.mount()
return () => {
client.unmount()
}
}, [client])
...//에러처리 코드 존재
const Context = getQueryClientContext(context, contextSharing)
return (
<QueryClientSharingContext.Provider value={!context && contextSharing}>
<Context.Provider value={client}>{children}</Context.Provider>
</QueryClientSharingContext.Provider>
)
//QueryClientProvider.tsx
function getQueryClientContext(
context: React.Context<QueryClient | undefined> | undefined,
contextSharing: boolean,
) {
if (context) {
return context
}
if (contextSharing && typeof window !== 'undefined') {
if (!window.ReactQueryClientContext) {
window.ReactQueryClientContext = defaultContext
}
return window.ReactQueryClientContext
}
return defaultContext
}
queryClient는 생성될때 queryCache라는 인스턴스를 생성한다.
queryCache 객체는 queries라는 배열과, queriesMap 이라는 객체를 맴버변수로 갖는다.
queryClient를 콘솔에 찍어보았을때 queryCache의 queries와 queriesMap을 보면 캐싱된 데이터를 조회 할 수 있다.(ReactQueryDevtools로도 볼 수 있다.)
//queryClient.ts
export class QueryClient {
private queryCache: QueryCache
constructor(config: QueryClientConfig = {}) {
this.queryCache = config.queryCache || new QueryCache()
this.mutationCache = config.mutationCache || new MutationCache()
this.logger = config.logger || defaultLogger
this.defaultOptions = config.defaultOptions || {}
this.queryDefaults = []
this.mutationDefaults = []
this.mountCount = 0
// ...에러처리 코드
}
//queryCache.ts
export class QueryCache extends Subscribable<QueryCacheListener> {
config: QueryCacheConfig
private queries: Query<any, any, any, any>[]
private queriesMap: QueryHashMap
constructor(config?: QueryCacheConfig) {
super()
this.config = config || {}
this.queries = []
this.queriesMap = {}
}
queries는 query들을 모아놓은 배열이고, queriesMap은 queryKey를 통해 query를 저장해놓은 객체이다.
결국 캐싱이 이루어지는 과정은, queryClient를 생성한 후 useQuery를 통해 data를 fetching 했을때, 이 데이터를 queryClient 내부에 있는 queryCache라는 객체에 저장해 놓는 것이였다.
더 자세한 동작 원리는 첨부한 아래 reference에 자세하게 설명되어 있다.
refetchOnMount: boolean | "always" | ((query: Query) => boolean | "always")
- 쿼리가 stale 상태일경우 마운트시 refetch를 실행하는 option이다.
- default로는 true 값을 가진다.
refetchOnWindowFocus: boolean | "always" | ((query: Query) => boolean | "always")
- window가 다시 focus 되었을때 (ex : 창을 이동해서 다시 웹을 열었을때, 커서가 다른페이지에 있다가, 다시 내부를 클릭했을때) 쿼리를 refetch하는 option이다.
- default로는 true 값을 가진다.
refetchOnReconnect: boolean | "always" | ((query: Query) => boolean | "always")
- 재연결되을때 데이터가 stale한 상태인경우 refetch를 진행하는 option이다.
- default로는 true값을 가진다.
retry: boolean | number | (failureCount: number, error: TError) => boolean
- 쿼리 요청에 실패했을때 refetching을 실행하는 option이다.
- false라면 실패한 경우 요청을 다시 보내지 않고, true라면 실패한 쿼리는 무한정 재시도한다. number로 설정하면 설정한 횟수만큼 재시도한다.
이러한 옵션들은 개발자가 서버상태를 어떻게 관리하고싶은지에 따라 다르게 적용한다.
예를 들어, 쿼리 데이터가 변하지 않는 정적인 데이터일때 refetchOnMount를 false로 두고, staleTime을 Infinity로 두어 캐싱된 데이터를 사용 할 수 있다.
또한 실시간으로 데이터가 변동된다면 refetchOnWindowFocus를 true로 두어 실시간으로 변하는 데이터를 update 할 수 있다.
staleTime은 기본적으로 cacheTime과 동일하게 5분으로 설정하였고, default값으로 refetchOnWindowFocus, refetchOnReconnect는 false로 설정하였다.
실시간으로 변하는 정보가 아니면 mount시 쿼리가 stale한 상태라면 재 호출되어 정보를 update해주는 과정이 필요했기 때문에 staleTime을 5분으로 설정하였다.
// App.tsx
const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
refetchOnReconnect: false,
retry: 1,
staleTime: 1000 * 60 * 5,
cacheTime: 1000 * 60 * 5,
},
},
});
export const useShopQuery = (shopId: number) => {
const {
isLoading: isShopLoading,
isError: isShopError,
data: shopState = initialShopState,
isSuccess,
} = useQuery<Shops, AxiosError>(['shop', shopId], () => fetchShop(shopId), {
refetchOnMount: false,
staleTime: Infinity,
cacheTime: Infinity,
});
return { isShopLoading, isShopError, shopState, isSuccess };
};
ref)