
Remote state를 관리하는 라이브러리다. 정식 이름은 Tanstack Query다.
주로 아래의 장점이 있다:
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000, // 쿼리가 최신 상태에서 더 이상 최신이 아닌 상태로 전환하는 시간
},
},
});
function App() {
return (
<QueryClientProvider client={queryClient}>
...
</QueryClientProvider>
);
}
위처럼 셋업한다.
const {
isLoading,
data: cabins, // 받아온 데이터를 cabins에 저장
error,
} = useQuery({
queryKey: ["cabins"],
queryFn: getCabins, // 데이터를 api에서 받아오는 함수
});
useQuery 훅이다. queryKey의 변화에 따라 쿼리가 업데이트된다. 로딩, 에러 상태를 쉽게 받아올 수 있다.
어떠한 Promise-based 메서드들과 사용이 가능하고, 서버의 데이터를 바꾸면 밑에서 얘기할 Mutation을 대신 사용한다.
쿼리에 사용하는 키는 내부적으로 refetching, caching, 쿼리 공유를 위해 사용된다.
const result = useQuery({ queryKey: ['todos'], queryFn: fetchTodoList })
위의 result 객체는 많은 상태들을 가지고 있는데, 아래와 같은 상태들만을 가진다.
isPending or status === 'pending'isError or status === 'error'isSuccess or status === 'success'const { status, data, error } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodoList,
})
if (status === 'pending') {
return <span>Loading...</span>
}
if (status === 'error') {
return <span>Error: {error.message}</span>
}
data의 상태를 불리안이 아닌 형태로 알려주는 status 상태도 있다.
status와 다르게, queryFn의 상태를 알려준다.
fetching, paused, idle이 있다.
// 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' }], ... })
위처럼 추가 id, 객체 등이 들어가 쿼리를 유일하게 표현하게 한다. 객체 내부의 키의 순서가 달라도 같은 객체로 치지만, array 내부의 아이템의 순서는 중요하다.
const queryClient = useQueryClient();
const { isLoading: isDeleting, mutate } = useMutation({
mutationFn: deleteCabin,
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ["cabins"],
});
},
onError: (err) => alert(err.message),
});
useQuery로 정의한 데이터를 update,delete해주는 훅이다.
받아온 데이터가 변경 성공시, 위 코드는 "cabins" 키로 캐시되어있는 데이터를 invalidate한다. 그러면 내부에서 데이터를 다시 fetch 해오기에 최신 데이터와 sync할 수 있다.
사용법은 비교적 직관적이다. 이제 언제나 그렇듯 뒤에서 무슨 일이 일어나길래 이런 마법같은 상태관리가 가능한지 보도록 하자.

사진 출처: 링크
우선 App 루트에 적용한 QueryClient가 핵심이다. 이는 QueryCache와 MutationCache의 컨테이너고, 쿼리와 뮤테이션들의 기본 설정값들을 보관한다. 보통 캐시와 직접 상호작용 할 일은 없고, QueryClient를 통해서 한다.

사진 출처: 링크
QueryCache는 queryKey로 저장한 캐시들이 Query 클래스의 인스턴스인 형태로 저장되어있는, 인-메모리 객체다. 중요한건 캐시는 인메모리로 저장된다는 것이고, 이때문에 새로고침을 하면 캐시가 날아가는 것이다. localstorage같은 다른곳에 저장하고 싶으면 persister를 사용해야 한다.

사진 출처: 링크
쿼리들은 쿼리에 대한 정보 뿐만 아니라 쿼리 함수, 재시도 등 정보도 포함중이다. 쿼리는 변경된다면 해당 변경사항들에 Subscribe된 Observer들에게 알려진다. Context API가 생각나는 부분이다.

사진 출처: 링크
쿼리들과 이를 사용하고 싶어하는 컴포넌트간 중간다리 역할을 하고있다. useQuery를 호출하면 만들어지고, 딱 한개의 쿼리에만 subcribe된다. 그래서 queryKey가 필요한 것이다!
Observer들은 컴포넌트가 쿼리의 어떤 prop들만 사용하는지 알기에, 지정된 필드 이외의 것들은 알려주지 않는다. data 필드만 사용한다면, isFetching이 바뀌었을때 컴포넌트가 재렌더링할 필요가 없다는 말이다.
이 Observer의 수는 devtool을 사용할때 쿼리 왼쪽에 있는 숫자로 알 수 있다.

사진 출처: 링크