기존의 fetch
를 사용한 네트워크 통신은 캐시가 되지않습니다. 네트워크 통신이 실패했을 때, 다시 재시도하는 기능도 없습니다. 이런 로직들은 개발자가 직접 작성해야 했으나 React Query
라이브러리의 도움을 받아 간편하게 해결할 수 있습니다.
React Query는 네트워크 통신이나 비동기적으로 상태를 관리, 서버의 데이터를 동기화 시켜주는 등 유용한 기능들을 제공합니다.
npm i @tanstack/react-query
# or
yarn add @tanstack/react-query
QueryClient
를 초기화 시킨 후 Provider
로 사용할 App을 감싸줍니다. 하위 컴포넌트에서는 커스텀 훅인 useQuery
를 이용하여 네트워크 통신을 할 수 있습니다.
import {
QueryClient,
QueryClientProvider,
useQuery,
} from '@tanstack/react-query'
const queryClient = new QueryClient()
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<Example />
</QueryClientProvider>
)
}
function Example() {
const { isPending, error, data } = useQuery({
queryKey: ['repoData'],
queryFn: () =>
fetch('https://api.github.com/repos/TanStack/query').then(
(res) => res.json(),
),
})
if (isPending) return 'Loading...'
if (error) return 'An error has occurred: ' + error.message
return (
<div>
<h1>{data.name}</h1>
<p>{data.description}</p>
<strong>👀 {data.subscribers_count}</strong>{' '}
<strong>✨ {data.stargazers_count}</strong>{' '}
<strong>🍴 {data.forks_count}</strong>
</div>
)
}
리액트 쿼리는 키에 의존해서 캐시를 관리합니다. 따라서 캐시를 잘 관리하려면 고유한 키를 잘 명시하고 분리하여 사용해야합니다. 키는 배열로 설정합니다.
세부적인 키 배열을 통해서 조건에 맞게 서로 다른 캐시를 사용할 수 있습니다.
// An individual todo
useQuery({ queryKey: ['todo', 5], ... })
// An individual todo in a "preview" format
useQuery({ queryKey: ['todo', 5, { preview: true }], ...})
// A list of todos that are "done"
useQuery({ queryKey: ['todos', { type: 'done' }], ... })
그러나 컴포넌트가 리렌더링 되거나 윈도우 창을 다시 포커스하게 되면, 캐시된 데이터를 사용하지 않고 네트워크 통신이 재발생하는 것을 볼 수 있습니다. 네트워크 통신으로 받아온 캐시된 데이터는 그 즉시 stale
한 상태가 되기 때문입니다.
확인해보기 위하여 리액트 쿼리에서 제공하는 개발 툴을 설치하고 데이터의 상태를 자세히 관찰할 수 있습니다.
리액트 쿼리는 개발 툴을 제공해줍니다. 다음과 같이 설치할 수 있습니다.
npm i @tanstack/react-query-devtools
# or
yarn add @tanstack/react-query-devtools
우리의 애플리케이션에는 다음과 같이 적용합니다.
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
function App() {
return (
<QueryClientProvider client={queryClient}>
{/* The rest of your application */}
<ReactQueryDevtools initialIsOpen={true} />
</QueryClientProvider>
)
}
로컬에서 애플리케이션을 실행하면 다음과 같이 개발 툴이 실행됩니다.
stale
에 활성화가 된 의미는 "이 데이터는 상태가 좀 오래되었다" 입니다.
stale
한 상태의 쿼리는 다음과 같은 상황들에 놓여질 때, 자동으로 백그라운드에서 refetch
됩니다.
이렇듯 예상하지 못한 refetch를 막기 위해서는 관련된 옵션들을 잘 사용해주어야 합니다.
refetchOnMount
refetchOnWindowFocus
refetchOnReconnect
refetchInterval
또는 StaleTime의 설정을 통해 refetch를 막을 수 있습니다.
빈번한 refetch를 방지하기위해 StaleTime
을 길게 지정해 줄 수 있습니다. 쿼리의 3번째 인자로 옵션 객체를 넘겨주면 됩니다.
const { isPending, error, data } = useQuery({
queryKey: ['repoData'],
queryFn: () =>
fetch('https://api.github.com/repos/TanStack/query').then(
(res) => res.json(),
),
{
staleTime: 5000
}
})
위의 쿼리는 5초 동안 fresh
한 상태를 유지하다가 stale
상태로 넘어갑니다. fresh한 상태에서는 refetch 되지 않습니다.
만약 컴포넌트에서 useQuery
또는 useInfiniteQuery
를 사용하지 않는 상태(inactive)가 5분 이상 지속된다면, 가비지컬렉터에 의해 캐시가 삭제됩니다. 좀 더 오래 유지하기 위해서는 CacheTime
을 더 긴 시간으로 바꿔줄 수 있습니다.
하지만 캐시 시간을 너무 길게 가져가면 업데이트를 놓칠 수 있습니다. 이럴때는 invalidateQueries
를 이용하여 해당하는 키의 쿼리를 수동으로 refetch할 수 있습니다.
쿼리의 네트워크 통신이 실패한다면, 3번의 재시도를 진행합니다. 각 시도마다 시간 간격의 차이가 있습니다. 이런 간격도 retry
, retryDelay
옵션들을 통해서 설정할 수 있습니다.