Tanstack Query (React Query)
Tanstack Query v4 기준으로 작성된 글입니다.
Tanstack Query(기존의 React Query)는 흔히 웹 애플리케이션을 위한 데이터 가져오기 라이브러리로 설명되지만, 보다 기술적인 측면에서 보면 웹 애플리케이션에서 서버 상태를 쉽게 가져오기, 캐싱, 동기화 및 업데이트할 수 있게 해줍니다.
Tanstack Query 공식 홈페이지의 원문을 DeepL로 번역한 결과이다.
Tanstack Query로 서버 상태를 관리할 때, 상황에 따라 서버의 데이터와 항상 동기화 해야할 때가 있고, 적당한 시간동안 클라이언트에서 데이터를 캐싱한 뒤 특정 조건에서 서버 상태와 다시 동기화 해도 될 때가 있다.
적절한 상황에서 캐싱을 사용해 서버 상태를 관리한다면 서버 요청 비용을 절약할 수 있다.
서버 요청 비용을 절약하면 서버 부하를 줄이고 응답 속도를 높일 수 있다. 또한, 클라우드 서비스 비용을 절감하고 네트워크 대역폭 사용을 최소화해 더 원활한 사용자 경험을 제공할 수 있다.
캐싱 잘 사용하기
staleTime과 cacheTime
staleTime
은 데이터가 얼마나 오래 신선한 상태로 간주될지 설정하는 속성이다. 이름 그대로, "stale"은 신선하지 않음을 뜻하며, 이 시간 이후 데이터는 신선하지 않은 것으로 판단된다.
cacheTime
은 캐싱된 데이터를 얼마나 오래 메모리에 보관할지 설정하는 속성이다. 이 속성의 원래 이름은 gcTime
(Garbage Collection Time)으로, 캐싱 데이터를 삭제하기 전까지의 시간을 의미한다.
cacheTime
은 반드시 staleTime
보다 같거나 커야 한다. 캐싱된 데이터가 메모리에 있어야만 그것이 신선한 상태인지, 신선하지 않은 상태인지 판단할 수 있기 때문이다.
이 두 속성은 비슷해 보이지만 서로 다른 역할을 한다. staleTime
은 데이터를 새로 요청할 필요가 있는지 결정하고, cacheTime
은 데이터를 메모리에서 유지할 시간을 결정한다.
staleTime < cacheTime
staleTime
이cacheTime
보다 작은 경우staleTime
이 지나면cacheTime
이 지나면 데이터는 stale 상태로 간주된다. 그러나 cacheTime이 지나기 전까지 데이터는 메모리에 남아 있어 사용할 수 있다.
staleTime === cacheTime
staleTime
이cacheTime
과 같은 경우cacheTime
동안 fresh한 데이터를 유지한다. (invalidQueries
와 같이 고의적으로 stale하게 변경하지 않는 이상)- 이 설정은 명확하고 직관적이며, 데이터가 일정 기간 동안만 유효하길 원하는 경우 적합하다.
staleTime > cacheTime
staleTime
이cacheTime
보다 큰 경우cacheTime
이 지나면 데이터는 메모리에서 삭제된다.- 이 설정은 일반적으로 적합하지 않다. cacheTime이 지나 데이터가 삭제되면 이후의 staleTime은 아무런 의미가 없기 때문이다.
로딩 중일 때 보여줄 화면
stale한 데이터를 보여주는 경우
오래된 데이터를 보여주는 것이 로딩 화면 또는 빈 화면을 보여주는 것보다 더 나을 때 유용하다.
오래된 데이터를 잠깐 보여주는 것이 UI에 긍정적일지 판단한 후 적용하는 게 좋다.
- 프레이밋 프로젝트에서 필터링 예시
- 필터 조건에 따라 쿼리를 캐싱하는 상황
- 전체 필터를 조회한 후 작가 필터를 조회함. 각 필터 조건의 리스트가 캐싱됨
- 작가 필터 화면에서 특정 아이템을 북마크함 +
invalidateQueries
로 모든 필터 조건에 따른 데이터를 invalidate 시킴. - 전체 필터 데이터를 조회함 → invalidate(북마크하기 전의) 데이터가 refetch되기 전까지 잠깐 보여짐 → refetch 완료된 후 바로 새로운 데이터가 보임
개인적으로는 위와 같 은 깜빡임은 없는 게 나아보인다. 이런 개발과 UI의 간극은 기획자 혹은 디자이너와의 적절한 소통을 통해 메꾸는 게 중요한 것 같다.
stale한 데이터를 화면에 보여주고 싶지 않을 경우
-
isStale
속성 사용isStale은 쿼리 데이터가 stale한지 확인할 수 있다. stale할 때 화면에 로딩 스피너를 보여주고 싶다면 다음과 같이 코드를 작성할 수 있다.
const { data, isLoading, isStale, } = useRecruitmentsQuery(filter); /* 중략 */ return ( <> {isLoading || isStale ? ( <LoadingSpinner /> ) : ( <div>{쿼리 데이터 컴포넌트}</div> )} </> );
-
cacheTime
과staleTime
을 동일하게 +removeQueries
사용만약
cacheTime
과staleTime
이 동일하게 설정되어 있는 상황일 때는invalidateQueries
가 실행되기 전까지 캐싱 데이터가 fresh한(stale하지 않은) 상태로 살아있다. 이럴 때 stale한 데이터가 필요 없다면removeQueries
로 캐시 데이터를 삭제할 수 있다.
keepPreviousData
keepPreviousData
는 같은 쿼리의 쿼리키가 변경될 경우 이전에 요청했던 데이터를 새로운 데이터를 받기 전까지 보여준다.
keepPreviousData
는 쿼리 키가 변경될 때 이전 데이터를 새로운 데이터가 도착할 때까지 유지한다.
필터링, 정렬 등의 변화로 쿼리 키가 변경되었을 때, 이전 데이터를 보여주어 UI가 깜빡이거나 불필요한 로딩 스피너를 표시하는 상황을 방지할 수 있다.
이것도 마찬가지로 로딩 화면이 적당할지, 다른 쿼리키의 데이터가 보여져도 괜찮을지 판단이 필요하다.
export const useRecruitmentsQuery = (filter: IRecruitFilter) => {
return useQuery({
queryKey: [RecruitmentQueryKey.RECRUIT_ANNOUNCEMENTS, filter],
queryFn: async () => {
const recruitData = await getRecruitAnnouncements(filter);
return recruitData.map(decodeRecruitResToRecruitProject);
},
staleTime: 60 * 1000 * 5, // 5분
// keepPreviousData: false,
// keepPreviousData: true,
});
};
keepPreviousData
:false
인 경우 (기본값)
keepPreviousData
:true
인 경우
백그라운드 데이터
refetchType
invalidateQueries
를 호출하면 특정 쿼리를 stale 상태로 표시하고, 필요 시 refetch를 실행할 수 있다. 기본적으로 refetch는 staleTime
과 관련된 동작을 따르므로, 즉시 데이터를 다시 가져오려면 추가 옵션을 설정해야 한다.
queryClient.invalidateQueries(['queryKey'], {
refetchType: 'active', // 'active', 'inactive', 'all' 중 선택
});
refetchType 옵션은 다음과 같다.
- 'active' (기본값): 현재 활성화된 쿼리(사용 중인 쿼리)만 refetch.
- 'inactive': 비활성화된 쿼리만 refetch.
- 'all': 활성화 및 비활성화된 모든 쿼리를 refetch.
비활성화된 쿼리를 refetch할 때는 해당 쿼리가 다시 요청될 가능성을 신중히 판단해야 한다. 예를 들어, N개의 필터가 각각 M개의 선택지를 가진다면, 가능한 모든 조합의 경우의 수는 M^N이다. 이러한 모든 쿼리를 한꺼번에 refetch하면 네트워크에 과도한 부하가 발생해 비효율적일 수 있다.
마무리
Tanstack Query를 사용하며 서버 상태를 쿼리라는 개념으로 관리하는 방법을 이해하게 되었다.
캐시 데이터를 어떻게 관리하느냐에 따라 UI와 사용자 경험이 크게 달라질 수 있다는 점이 특히 흥미로웠다. 이를 통해 서버 상태 관리가 단순히 데이터를 가져오고 저장하는 과정이 아니라, 사용자에게 더 나은 경험을 제공하기 위한 중요한 설계 요소임을 깨닫게 되었다.
앞으로도 이러한 경험을 바탕으로 더 효율적인 데이터 관리와 매끄러운 UI를 구현하는 방법을 고민하고 발전시켜 나갈 생각이다.