TanStack-Query에서는 useQuery를 이용해 서버의 data를 받아올 수 있고, data의 staleTime과 gcTime을 설정해줌으로써 서버 data의 상태를 관리할 수 있다. 이러한 staleTime, gcTime과 invalidQueries를 활용해 어떻게 서버 data의 상태 관리를 효율적으로 할 수 있는지 알아보자.
캐시된 data가 신선한 상태(fresh)로 남아있는 시간을 말한다.
특정 data에 대해 설정해준 stale time이 지나게되면, 그 data는 신선하지 않은 상태(stale)로 간주된다.
따로 stale time의 값을 설정해주지 않으면 기본값은 0
이다.
=> data를 fetch 해오자마자 data를 신선하지 않다고 간주하는 것 !
특정 쿼리 키에 대한 data를 다시 fetch 해와야 하는 상황일 때,
=> 즉, 서버로부터 새로 data를 받아오지 않고 캐시된 data를 최신의 data로 간주하고 사용하기 위해 설정해주는 조건이다.
메모리에 저장된 캐싱 데이터가 유효한 시간(캐시 메모리에 남아있는 시간)을 의미한다.
쿼리를 사용하는 모든 컴포넌트가 언마운트되었을 때, 쿼리는 비활성화(inactive 상태)되고 비활성화된 데이터는 gcTime
이 지난 후, 캐시에서 삭제된다.
쿼리를 사용하는 컴포넌트가 언마운트 되지 않고 계속 마운트된 상태로 화면에 계속 보여진다면, cache data는 계속 남아있다.
=> 쿼리를 사용하는 모든 컴포넌트가 언마운트 된 이후부터
gcTime
이 지나야 캐시 데이터가 삭제되기 때문이다.
캐싱 데이터가 없다면, 특정 query key에 해당하는 쿼리 호출 시, 다시 API 요청을 통해 data를 받아온다.
따로 gcTime의 값을 설정해주지 않으면 기본값은 5분
이다.
그렇다면 staleTime과 gcTime가 어떻게 설정되어있는지에 따라 data fetch가 어떻게 달라지는지 각 상황을 통해 알아보자.
특정 data를 fetch 할 때
staleTime 아직 안 지남, gcTime 아직 안 지남
=> API 요청 X, 캐시 데이터 사용
staleTime 아직 안 지남, gcTime 지남
=> staleTime은 아직 지나지 않아서 cache data를 최신의 data로 간주하고 사용하고 싶은데 gcTime이 지나서 cache에 data가 없는 상황
=> API 요청으로 새로운 data 받아와야 함
사실 이런 경우는 해당 쿼리가 포함된 페이지나 컴포넌트가 새로 다시 mount되는 경우일 것이다.
=> 만약 unmount 되지 않고 계속 해당 쿼리가 활성화 상태라면 cache에 data가 계속 있는 상태였을 것이므로
=> unmount 되었다가 다시 mount되어 다시 data fetch 하는 경우일 듯
EX)
export const useGetContentList = (type: string, title: string, page: number) => { const { data } = useQuery( [{ scope: "ContentList", type, title, page }], () => { console.log(`fetch Data: ${type},${title}, ${page}`); return getContentList(type, title, page); }, { staleTime: 1000000, gcTime: 100, select: (data) => { return data?.contentList; }, } ); return { contentListData: data, }; };
위와 같이
gcTime
을 엄청 짧게 설정해, 쿼리가 inactive된 후 바로 삭제되도록 했을 때, 다시 해당 쿼리가 불렸을 때 다시 data를 fetch해오고 이를 cache에 추가하는 것을 확인할 수 있었다.
staleTime 지남, gcTime 아직 안 지남
=> API 호출로 새로운 신선한 data를 받아오는데, 받아오기 전까지는 cache data를 보여줌
여기서 해당 useQuery에 대한
isLoading
값은 계속 false이기 때문에 사용자는 loading 화면을 보고 있지 않아도 된다.
staleTime 지남, gcTime 지남
=> API 호출로 새로운 신선한 data를 받아오고, 받아오기 전까지는 보여줄 data 없음
위의 각 상황들을 정리해보면 !
fetch나 refetch를 통해 data를 불러올 때
- staleTime 안 지나면 cache data 사용
- gcTime 지나서 cache data 없으면 => refetch
- staleTime 지나면 무조건 새로운 data 받아오는 것
- 새로운 data 받아오는 동안, cache data가 있으면 이를 보여주다가 새로운 data로 교체해주는 것
tanstack query는 특정 쿼리를 무효화해주는 invalidQueries
메서드를 제공한다.
// 캐시의 모든 쿼리를 무효화함
queryClient.invalidateQueries();
// `todos`로 시작하는 쿼리키를 가지는 모든 쿼리를 무효화함
queryClient.invalidateQueries({ queryKey: ['todos'] });
쿼리를 무효화한다는 것은 무슨 뜻일까 ? invalidateQueries
메서드를 통해 특정 쿼리를 무효화하면, 다음과 같은 두 가지 현상이 발생한다.
useQuery
또는 관련 훅에서 사용 중인 모든 staleTime
구성에 오버라이드한다.useQuery
나 관련 훅을 통해 쿼리가 렌더링되고 있다면 백그라운드에서도 refetch
한다.=> 그래서 invalidQueries
메서드를 이용해 특정 쿼리를 무효화하면, 해당 쿼리의 data를 신선하지 않은 상태로 간주해 이후 data를 fetch 할 때 캐시의 data가 아닌 서버로부터 새로운 data를 받아올 수 있도록 하는 것이다.
문자열
, 문자열의 배열
혹은 중첩된 객체(nested object)
로 지정 가능하다.공식 문서에서는 다음과 같이 쿼리 키를 지정하도록 권장한다.
쿼리 키는 쿼리 function에 대한 dependencies의 역할을 한다. 쿼리 function이 의존하고 있는 변수들을 쿼리 키 배열에 넣음으로써, 각 쿼리 키에 해당하는 쿼리들이 독립적으로 cache되고, 변수가 변할 때마다 각 쿼리들이 자동적으로 refetch 되도록 할 수 있다.
staleTime이 0
으로 설정되어있다면 data를 fetch 하는 즉시 stale
한 상태로 변하게 되고, refetch
가 일어나는 조건과 일치하게 되면 데이터가 패치됩니다.
즉, staleTime이 지나 data가 stale 한 상태가 되더라도, 바로 refetch가 일어나는 건 아니다.
만약 그렇다면 기본 staleTime은 0인데 페이지에 그대로 있어도 data가 무한으로 계속 refetch 될 것이다.
=> staleTime이 지나고 && refetch가 일어나는 조건에 해당될 때 refetch가 일어나는 것
refetch의 전제조건은 데이터가 stale
한 상태여야 한다.
⇒ 다른 페이지 갔다가 와서 새로 mount 되더라도, data가 fresh
하다면 refetch 하지 않는다. (ex) 작성하고 다시 홈페이지가면 업데이트 x)
=> 대신 새로고침할 때는 무조건 refetch 된다. ( refetch라고 부르는 것이 올바르지 않은 듯)
위에서 살펴본 queryKeys, staleTime, InvalidQueries의 개념을 서로 연결해 data의 상태를 어떻게 효율적으로 관리할 수 있는지 알아보자 !
특정 data가 자주 변경되지 않고, 특정 상황에서만 변경이 된다면 다음과 같은 방법으로 data를 관리할 수 있다.
infinity
로 설정invalidQueries
실행이를 통해 , 특정 페이지에 접근할 때마다 무조건 서버로부터 data를 받아오는 것이 아니라, data가 변경이 되었을 때만 서버에서 새로운 data를 받아옴으로써 불필요한 API 호출을 줄일 수 있다.
실제 나의 프로젝트에서 적용했던 예시를 살펴보면,
infinity
로 설정해주고export const useGetMyPlansQuery = () => {
const { data } = useSuspenseQuery({
queryKey: [QUERY_KEY.MY_PLANS],
queryFn: getMyPlans,
staleTime: Infinity,
});
return { myPlans: data! };
};
export const usePostNewPlanMutation = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: postNewPlan,
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: [QUERY_KEY.MY_PLANS],
});
},
});
};
여기서 쿼리키를 기반으로 특정 쿼리만 무효화시켜주었음을 확인할 수 있다.
queryClient.invalidateQueries({ queryKey: [QUERY_KEY.MY_PLANS], });