useQuery 훅의 파라미터를 통해 API 데이터의 만료 시간, 리프레시 간격, 캐싱된 데이터의 유지 기간, 브라우저 포커스시 데이터 리프레시 여부, 성공 or 에러 콜백 등 다양한 기능을 제어할 수 있다.
stale
외부 요청으로 서버 데이터가 변경될 때, 내 브라우저가 가진 데이터는 이미 오래된 데이터가 되었으므로(받았을 당시의 snapshot), 이 상태를 stale 하다고 한다.
리액트는 캐싱된 데이터의 상태를 stale(탁한, 낡은)로 여기고, stale 한 상태의 경우 아래의 조건을 만족하면 refetch 된다
- 새로운 query instance 가 마운트될 때(page 이동 후 다시 왔을 때)
- 브라우저 화면을 이탈했다가 다시 focus 할 때
- 네트워크가 끊겼다가 다시 연결될 때
- 특별히 설정한 refetch interval 에 의한 경우
query 에 별다른 action 이 없으면( 활성화된 useQuery, useInfiniteQuery 인스턴스가 없는 쿼리 결과 ) inactive 상태로 캐시에 있다가, 5분 뒤에 메모리에서 사라진다. cacheTime 옵션을 통해 이 시간을 조정할 수 있다
백그라운드에서 3회 이상 실패한 쿼리는 에러 처리된다. retry 옵션을 통해 오류 발생 시 재시도 할 횟수를 정할 수 있고, retryDelay 옵션으로 재시도 대기 시간을 설정할 수 있다.
refetchOnWindowFocus 등의 옵션으로 refetch 설정을 막을 수 있고, staleTime 옵션으로 설정한 시간 동안 stale 되지 않도록 하여 refetch 를 막을 수도 있다
staleTime
- 데이터가 fresh -> stale 상태로 변경되는 데 걸리는 시간으로, defaultValue 는 0 이다. 고로, staleTime 을 지정해주지 않는다면, react-query 의 캐싱 기능을 제대로 사용할 수 없다는 말이다. 받아오는 즉시 stale 상태인 것.
- fetch 되고 나서 staleTime 이 지나지 않았다면, unmount 후 mount 되어도 fetch 가 일어나지 않는다.
- fresh 상태일 땐 쿼리 인스턴스가 새롭게 mount 되어도, fetch 가 일어나지 않는다
- 데이터가 inactive 상태일 때 캐싱된 상태로 남아있는 시간으로, defaultValue 는 5분이다. 캐시 구조에 저장된 데이터는 메모리 상에 존재하게 된다
- cacheTime 이 지나기 전에 쿼리 인스턴스가 다시 mount 되면, 데이터를 fetch 하는 동안 캐시 데이터를 보여준다
- cacheTime 은 staleTime 과 관계 없이, 무조건 inactive 시점을 기준으로 캐시 데이터 삭제를 결정한다. 즉, staleTime 이 길어도 cacheTime 이 짧다면 데이터는 사라진다
- cacheTime 이 지나면, 데이터는 가비지 콜렉터로 수집된다
const { data } = useQuery('users', getUsers, {
// options
staleTime: 5000,
cacheTime: Infinity,
})
import { QueryClient, QueryClientProvider } from 'react-query'
const queryClient = new QueryClient() // 인스턴스 생성
root.render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</React.StrictMode>
)
- queryKey 에는 문자열과 배열을 넣을 수 있고, 이 유연함이 캐싱 처리를 쉽게 해준다. 쿼리 키가 다르면, 캐싱 관리도 별도로 하기 때문이다.
- queryFunction 에는 서버 데이터를 요청하고, Promise 를 리턴하는 함수를 전달한다. axios.get(...), fetch(...) 등의 함수.
const { data, isLoading } = useQuery(queryKey, queryFunction, options)
// 객체 필드의 내용이 같으면 순서가 달라도 같은 키로 취급한다
useQuery(['todo', { preview: true, status: 'done' }], ...)
useQuery(['todo', { status: 'done', preview: true }], ...)
// 객체 필드의 값이 다르면 다른 키로 취급한다
useQuery([{ preview: true, status: 'done' }], ...)
useQuery([{ preview: false, status: 'done' }], ...)
const queryClient = new QueryClient({
defaultOptions : {
queries : {
refetchOnWindowFocus : false,
refetchOnMount : false,
retry : false
}
}
})
useQuery 의 refetch 함수
- 캐싱 결과는 조회하지 않고 무시한 채, 요청을 날리는 메서드. QueryClient 객체에 저장된 캐싱 내용에 해당 요청값의 키가 존재한다 하더라도, re-fetch 요청을 진행한다.
- 결론적으로, 캐싱 구현을 하기 위해서는 enabled 옵션을 false로 두면 안된다.
useQuery 에서 'enabled: ture' 상태와 캐싱을 둘 다 구현하려면?
- enabled option 에 대해, 특정 상태를 충족할 때만 true 로 만든다. 그 외에는 false 상태를 유지하여 refetch 메서드 강제 호출을 막는다.
- enabled true 일 때 요청을 성공하게 되면, 값이 캐싱될 것이다
react-query/devtools
react-query 를 설치할 때 함께 받아지는 것으로, 따로 설치할 필요가 없다. 페이지 루트에 가까울수록 더 잘 동작한다고 함. production 모드일 땐 번들에 포함되지 않으므로, 신경쓰지 않고 개발하면 된다.
import { ReactQueryDevtools } from 'react-query/devtools'
...
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
)
공공 데이터는 처음 받아 보는데, " 데이터 요청 시 공공데이터 API 앞에 "https://cors-anywhere.herokuapp.com/"을 작성하기 " 같은 방식으로 CORS 에러가 해결될 수 있다는 걸 알았다. 배포 시에도 문제없는 방법일지는 해 봐야 알 것 같다.
폴더 구조를 NavBar/NavBar.tsx 이런식으로만 만들었는데, NavBar/index.tsx 이렇게 만들면 import 길이가 줄어든다.
// NavBar/NavBar.tsx 일 경우
import NavBar from 'components/NavBar/NavBar'
// NavBar/index.tsx 일 경우
import NavBar from 'components/NavBar'
'이렇게 하면 되지 않을까요? 해 볼까요?' 말고, '이렇게 하시면 될 겁니다' 하는 사람이고 싶다