
웹 애플리케이션에서 서버 데이터를 패칭하고, 캐싱하고, 업데이트하는 과정은 개발이 복잡하고, 오류 발생 가능성이 높은 부분 중 하나이다. 오늘은 이 작업들을 매우 간편하게 만들어주는 라이브러리인 TanStack Query에 대해 공부해보겠다.
나는 첫 프로젝트부터 TanStack Query를 사용해왔지만, 솔직히 제대로 이해하고 쓰진 않았던 것 같다. 처음 도입하게 된 이유는 useEffect + fetch + setIsLoading + setIsError를 내가 직접 작성할 필요가 없어서 편할 것 같았기 때문이었다. 여태까지 어찌저찌 써오긴 했지만, 뭔가 도구에 대한 제대로 된 이해없이 막 쓰고 있다는 생각이 들었다. queryKey를 설계하는 방법도 잘 모르고, 캐시 전략에 대한 개념도 부족했다. invalidate 범위를 잘못 설정해서 캐시 업데이트에 실패해서 사용자에게 stale한 데이터도 자주 보여줬다...
그래서 순서가 조금 바뀌긴 했지만, 공식문서와 여러 자료를 정독하면서 TanStack Query의 원리와 컨셉을 체계적으로 이해하고, 정리해보기로 했다.
서론이 너무 길었다😵💫!! 서버 상태부터 이해해보자.
서버 상태란 서버에 저장된 데이터로 프론트엔드에서 api호출을 통해 백엔드로부터 받아오는 데이터들을 의미한다. 서버 상태는 관리하기 까다로운 몇 가지 특징을 가진다.
서버 상태는 useState와 같은 훅 또는 Redux, Zustand와 같은 라이브러리를 사용해 프론트엔드에서 다루는 클라이언트 상태와는 근본적으로 다른 상태임을 이해해야 한다.
클라이언트 상태는 브라우저 내에만 존재하는 반면에, 서버 상태는
이다. 서버 상태는 클라이언트 상태와 달리 TanStack Query라이브러리를 사용해 관리한다.
TanStack Query는 위에서 말했던 서버 상태의 까다로운 특징에서 비롯되는 관리의 어려움을 해결해주는 프레임워크에 구애받지 않는 라이브러리이다.
공식문서에 따르면 다음과 같은 명령어로 설치할 수 있다.
npm i @tanstack/react-query // TanStack Query
npm i @tanstack/react-query-devtools // 개발자 도구
npm i -D @tanstack/eslint-plugin-query // ESLint 플러그인 쿼리
설치를 완료했으면 TanStack Query라이브러리를 사용하기 위해서는 최상단 컴포넌트의 반환값을 QueryClientProvider로 감싸고
QueryClient 객체를 생성해서 전달해야한다.
QueryClient는 TanStack Query의 중앙 저장소 역할을 하는데, 조금 더 구체적으로 작성하면 전체 캐시를 보관하고, staleTime과 gcTime의 설정을 관리한다. 또한 background refetch를 스케줄링하고 query 상태를 전파하는 역할도 담당한다고 한다.
다음 코드처럼 모든 컴포넌트의 root 컴포넌트인 App 컴포넌트를 QueryClientProvider로 감싸고, QueryClient객체를 전달하면,
App 컴포넌트의 하위컴포넌트들은 모두queryClient에 접근할 수 있게 된다.
import {
QueryClient,
QueryClientProvider
} from '@tanstack/react-query'
import { ReactQueryDevTools } from '@tanstack/react-query-devtools'
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1 * 60 * 1000, // 1분 (기본값 0)
gcTime: 5 * 60 * 1000, // 5분 (기본값 5분)
retry: 3, // 실패시 재시도 3회
},
},
})
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
{/* 2. Wrap your App with QueryClientProvider */}
<QueryClientProvider client={queryClient}>
<App />
{/* 3. Add Devtools for easy debugging (optional, only in development) */}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
</React.StrictMode>,
);
이것이 가능한 이유는 QueryClientProvider가 React의 Context API를 기반으로 만들어졌기 때문이다! QueryClientProvider는 내부적으로 QueryClient 객체를 Context에 담아 하위 컴포넌트 전체에 공급하고, useQuery나 useMutation 같은 훅들은 내부적으로 useContext를 통해 이 QueryClient를 꺼내 사용한다. 덕분에 중간 컴포넌트를 거치지 않고도 트리 어느 깊이에서든 캐시와 쿼리 상태에 접근할 수 있다.
개발자 도구를 사용하고 싶다면 QueryClientProvider아래에 ReactQueryDevtools를 두면 된다. 더 자세한 건 공식문서를 참고하면 알 수 있다.
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
function App() {
return (
<QueryClientProvider client={queryClient}>
{/* The rest of your application */}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
)
}
initialIsOpen={false}: 페이지 로드시 자동 팝업되지 않는다.DevTools를 통해서는 다음 정보를 확인할 수 있다.
🟢 Fresh: 즉시 캐시 반환 (staleTime 내)
🟡 Stale: 백그라운드 refetch 중
⚪ Inactive: gcTime 카운트다운 중
🔴 Error: 네트워크 오류