리액트에서 서버 데이터 가져오기, 캐싱, 동기화 및 업데이트를 쉽게 다룰 수 있도록 도와주는 라이브러리이다.
기존 상태 관리 라이브러리인 redux, recoil 등은 클라이언트 상태 작업에 적합하지만, 비동기 또는 서버 상태 작업에는 그다지 좋지 않다고 한다.
💡 React Query v4부터는 TanStack Query로 라이브러리 이름이 변경되었다.
queryKey, queryFn가 필수 값이다.import { useQuery } from '@tanstack/react-query';
// queryFn의 반환 타입을 지정해주면 useQuery의 타입 추론이 원활해진다.
const getPosts = async(): Promise<AxiosResponsePost[]>> {
return await axios.get(`${BASE_URL}/posts`);
}
function HomePage() {
const {data, isLoading} = useQuery({
queryKey: ['posts'], // 필수 값
queryFn: getPosts // 필수 값
// ...options ex) geTime, staleTime, select 등등...
});
return <div>홈페이지</div>;
}
export default HomePage;
1. queryKey
const getPosts = async ({
queryKey
}: {
queryKey: ["posts", number]
}): Promise<AxiosResponse<post>> => {
const postId = queryKey[1]; // ex) queryKey: ["posts", "3"]
return await axios.get(`${BASE_URL}/posts`);
};
const usePostData = (number: string) => {
return useQuery({
queryKey: ["posts", postId],
queryFn: getPosts,
});
};
💫 useQuery는 queryKey를 기반으로 쿼리 캐싱을 관리하는 것이 핵심이다.
ex) ["posts, postId, ..."]queryClient.setQueryData 등과 같이 특정 쿼리에 접근이 필요할 때 초기에 설정해둔 포맷을 지켜줘야 제대로 쿼리에 접근할 수 있다.usePostData의 queryKey는 ["posts", postId]이다. queryClient.setQueryData를 이용할 때 똑같이 ["posts", postId] 포맷을 가져야 한다. 그렇지 않으면 원하는 쿼리에 접근할 수 없다.2. queryFn
useQuery의 queryFn는 Promise를 반환하는 함수를 넣어야 한다.// 위에 queryKey 예제와 반대로 queryFn 자체적으로 인자를 받는 형태
const getPosts = async (postId: string): Promise<AxiosResponse<Post>> => {
return await axios.get(`${BASE_URL}/posts`);
};
const usePostData = (postId: string) => {
return useQuery({
queryKey: ["posts", postId],
queryFn: () => getPosts(postId),
});
};
data: 쿼리 함수가 리턴한 Promise에서 resolved된 데이터
error: 쿼리 함수에 오류가 발생한 경우, 쿼리에 대한 오류 객체
status: 쿼리 결과 값에 대한 상태를 표현하는 status는 문자열 형태로 3가지의 값이 존재한다.
{ enabled: false } 상태로 쿼리가 호출되면 이 상태로 시작한다.fetchStatus: queryFn에 대한 정보를 나타냄
isLoading: 캐싱 된 데이터가 없을 때 즉 처음 실행된 쿼리일 때 로딩 여부에 따라 true/false로 반환된다.
false를 반환한다.isFetching && isPending과 동일하다.isFetching: 캐싱 된 데이터가 있더라도 쿼리가 실행되면 로딩 여부에 따라 true/false로 반환된다.
isSuccess: 쿼리 요청에 성공하면 true를 반환한다.
isError: 쿼리 요청 중에 에러가 발생한 경우 true를 반환한다.
refetch: 쿼리를 수동으로 다시 가져오는 함수이다.
그 외 반환 데이터들을 자세히 알고 싶다면 useQuery 공식 문서를 참고하면 된다.
fetchStatus는 HTTP 네트워크 연결 상태아 좀 더 관련된 상태 데이터이다.
status가 success 상태라면 주로 fetchStatus는 idle 상태지만, 백그라운드에서 refetch가 발생할 때 fetching 상태 일 수 있다.
status가 보통 loading 상태일 때 fetchStatus는 주로 fetching 상태를 갖지만, 네트워크 연결이 안되어 있는 경우 paused 상태를 가질 수 있다.
📝 정리하기
queryFn 요청이 진행 중인지 아닌지에 대한 상태를 의미한다.썩은이라는 의미이다. 즉 최신 상태가 아니라는 의미이다.신선한이라는 의미이다. 즉 최신 상태를 의미한다.const {
data,
// ...
} = useQuery({
queryKey: ["posts"],
queryFn: getAllPosts,
gcTime: 5 * 60 * 1000, // 5분
staleTime: 1 * 60 * 1000, // 1분
});
staleTime: (number | Infinity)
fresh에서 stale 상태로 변경되는 데 걸리는 시간이다. 만약 staleTime이 3000이라면 fresh 상태에서 3초 뒤에 stale로 변환된다.fresh 상태일 때는 쿼리 인스턴스가 새롭게 mount 되어도 네트워크 요청(fetch)가 일어나지 않는다.stale 상태가 된다.gcTime: (number | Infinity)
inactive 상태일 때 캐싱 된 상태로 남아있는 시간이다.inactive 상태로 변경되며, 캐시는 gcTime만큼 유지된다.inactive 된 시점을 기준으로 캐시 데이터 삭제를 결정한다.🚨 주의 사항
staleTime과 gcTime의 기본 값은 각각 0분, 5분이다. 따라서 staleTime에 어떠한 설정도 하지 않으면 해당 쿼리를 사용하는 컴포넌트가 mount 됐을 때 매번 다시 API를 요청할 것이다.
staleTime을 gcTime보다 길게 설정하면, staleTime만큼의 캐싱을 기대했을 때 원하는 결과를 얻지 못할 것이다. 두 개의 옵션을 적절하게 설정해주는 것이 중요하다.
📝 정리하기
staleTime이 지나기전에 쿼리 인스턴스가 새롭게 mount되어도 API 요청을 보내지 않는다.
컴포넌트가 unmount 되면 쿼리가 inactive 상태가 되고 gcTime이 지나기 전에 다시 mount가 되는 경우에 API 요청을 보내지 않는다.
staleTime과 gcTime이 모두 지나고 데이터를 다시 fetch하는 경우에는 사실상 처음 데이터를 가져오는 것과 동일한 과정을 거친다.

데이터가 stale 상태일 경우, mount마다 refetch를 실행하는 옵션이다.
기본값은 true이다.
always로 설정하면 마운트 시마다 매번 refetch를 실행한다.
false로 설정하면 최초 fetch 이후에는 refetch 하지 않는다
데이터가 stale 상태일 경우 윈도우 포커싱 될 때마다 refetch를 실행하는 옵션이다.
기본값은 true이다.
크롬에서 다른 탭을 눌렀다가 다시 원래 보던 중인 탭을 눌렀을 때도 이 경우에 해당한다. 심지어 F12로 개발자 도구 창을 켜서 네트워크 탭이든, 콘솔 탭이든 개발자 도구 창에서 페이지 내부를 다시 클릭했을 때도 이 경우에 해당한다.
always로 설정하면 항상 윈도우 포커싱 될 때마다 refetch를 실행한다는 의미이다.
Polling(폴링)이란?
실시간 웹을 위한 기법으로일정한 주기(특정한 시간)를 가지고 서버와 응답을 주고받는 방식이 폴링 방식이다.
Reqct Query에서는refetchInterval,refetchIntervalInBackground을 이용해서 구현할 수 있다.
refetchInterval과 함께 사용하는 옵션이다.쿼리가 자동으로 실행되지 않도록 할 때 설정할 수 있다.
enabled를 false를 주면 쿼리가 자동 실행되지 않는다.
pending 상태로 시작한다.refetch는 쿼리를 수동으로 다시 요청하는 기능이다. 쿼리 오류가 발생하면 오류만 기록된다.
true로 해서 전달해야 한다.보통 자동으로 쿼리 요청을 하지 않고 버튼 클릭이나 특정 이벤트를 통해 요청을 시도할 때 같이 사용한다.
주의할 점은, enabled: false를 줬다면 queryClient가 쿼리를 다시 가져오는 방법 중 invalidateQueries와 refetchQueries를 무시한다.
쿼리가 실패하면 useQuery를 특정 횟수만큼 재요청하는 옵션이다.
retry가 false인 경우, 실패한 쿼리는 기본적으로 다시 시도하지 않는다. true인 경우에는 실패한 쿼리에 대해서 무한 재요청을 시도한다.
값으로 숫자를 넣을 경우, 실패한 쿼리가 해당 숫자를 충족할 때까지 요청을 재시도한다.
기본 값은 클라이언트 환경에서는 3, 서버 환경에서는 0이다.
placeholderData를 설정하면 쿼리가 pending 상태인 동안 특정 쿼리에 대한 placeholder data로 사용된다.
placeholderData는 캐시에 유지되지 않으며, 서버 데이터와 관계없는 보여주기용 가짜 데이터다.
placeholderData에 함수를 제공하는 경우 첫 번째 인자로 이전에 관찰된 쿼리 데이터를 수신하고, 두 번째 인자는 이전 쿼리 인스턴스가 된다.
v4까지 있던 keepPreviousData은 페이지네이션과 같은 기능을 구현할 때 많이 사용하던 옵션이었다. 캐싱 되지 않은 페이지를 가져올 때 목록이 깜빡거리는 현상을 방지할 수 있다.
이들은 각각 placeholderData와 isPlaceholderData 플래그와 거의 유사하게 동작하여 v5부터 keepPreviousData, isPreviousData은 옵션은 제거됐다.
아래 예시처럼 작성해서 위의 keepPreviousData와 동일한 동작을 할 수 있다.
const {
data,
// ...
} = useQuery({
queryKey: ["posts"],
queryFn: getAllPosts,
placeholderData: (previousData, previousQuery) => previousData,
});