TanStack Query란
TanStack Query(전 React-Query)는 HTTP 요청을 간편하게 전송할 수 있고 프론트엔드 UI를 백엔드 데이터와 동기화된 상태로 유지하는 데 도움을 주는 라이브러리이다.
즉, 데이터 가져오기(fetching), 캐싱(caching), 동기화하고(synchronizing) 서버 상태를 업데이트(updating)하는 작업을 도와주는 라이브러리이다.
useEffect나 fetch 함수로도 이런 작업을 할 수 있지만 tanstack query를 이용하면 코드가 훨씬 간결해지고 수월하게 작업할 수 있다.
설치
npm install @tanstack/react-query
사용하기
Tanstack query는 HTTP 요청을 보내는 로직이 내장되어 있지 않다. 따라서 요청을 전송하는 코드는 직접 작성해야 해야 한다.
그대신 요청을 관리하는 로직을 제공한다. 요청과 관련된 데이터와 발생 가능한 오류를 추적하는 역할 등을 한다.
Tanstack query를 사용하려면 쿼리 기능을 사용할 컴포넌트를 QueryClientProvider 컴포넌트로 감싸야 한다.
QueryClient, QueryClientProvider
import { QueryClientProvider, QueryClient } from '@tanstack/react-query';
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />;
</QueryClientProvider>
);
}
export default App;
QueryClient 생성자 함수를 호출해 queryclient를 생성하고 QueryClientProvider의 client prop의 값으로 queryClient로 설정한다.
이렇게 하면 이제 모든 컴포넌트가 tanstack query 기능을 이용할 수 있다.
useQuery로 데이터 가져오기
useQuery 훅은 자체적으로 작동해서 HTTP 요청을 전송하고 필요한 데이터를 가져오고 로딩 상태에 대한 정보를 제공한다.
useQuery에 객체를 전달하면 된다. 이 객체에 여러가지 프로퍼티를 설정할 수 있고 필수 프로퍼티는 다음과 같다.
- queryKey: 리액트 쿼리는 이 쿼리 키를 이용해 요청으로 생성된 데이터를 캐시 처리한다. 따라서 나중에 동일한 요청을 전송하면 이전 요청의 응답을 재사용할 수 있다.
- queryFn: 요청을 전송할 때 실행할 코드 정의, 반드시 Promise를 반환해야 한다.
const { data, isPending, isError, error } = useQuery({
queryKey: ['events'],
queryFn: fetchEvents,
});
이제 useQuery가 실행되면 객체를 얻을 수 있는데, 이 객체에는 다양한 프로퍼티가 존재한다.
- data: 쿼리 함수가 반환하는 실제 응답 데이터
- isError: 오류 응답을 받은 경우 True, 이때 queryFn에 설정한 함수에서 응답에 오류가 있는 경우 오류를 throw하도록 해서 이 오류가 throw되면 True가 되는 것이다.
- error: 발생한 오류에 대한 정보가 담긴 객체
- isPending: 요청이 실행 중인지, 응답을 받았는지 알 수 있다. 쿼리가 비활성화된 상태인 경우에도 True이다.
- isLoading: 요청이 실행 중인지의 여부, 쿼리가 비활성화됐다고 해서 True가 되지 않는다.
Tanstack 쿼리를 이용하면 리액트 애플리케이션의 페이지에서 벗어난 뒤 다시 페이지로 돌아오는 동작에 쿼리가 반응해서 HTTP 요청이 전송된다. 이것은 만약 백엔드에서 변경된 경우 업데이트된 데이터를 자동으로 가져온다는 장점이 있다.
Caching
리액트 쿼리는 응답 데이터를 캐시 처리한다. 따라서 다른 페이지로 이동했다가 다시 돌아오면 데이터가 즉각적으로 제공된다.
컴포넌트가 실행될 때 동일한 쿼리 키를 가진 useQuery가 실행되면 리액트 쿼리는 해당 쿼리 키의 데이터가 캐시 처리된 것을 확인하고 데이터를 즉시 제공하고, 내부적으로 쿼리 함수에 있는 요청을 다시 전송해서 업데이트된 데이터가 있는지 확인한다.
새 요청을 전송하는 시기 제어 (staleTime 설정)
캐시에 데이터가 있을 때 업데이트된 데이터를 가져오기 위한 요청을 전송하기 전에 기다릴 시간을 설정한다.
const { data, isPending, isError, error } = useQuery({
queryKey: ['events'],
queryFn: fetchEvents,
staleTime: 5000,
});
기본값은 0이고, 0일 때는 캐시 데이터를 사용하지만 업데이트된 데이터를 가져오기 위한 요청을 항상 전송한다.
만약 5000으로 설정하면, 5초 안에 다시 컴포넌트를 렌더링할 경우 리액트 쿼리는 요청을 전송하지 않는다.
staleTime을 설정하면 불필요한 요청 전송을 방지할 수 있는 것이다.
데이터를 보관하는 시간 제어 (gcTime 설정)
가비지 수집 시간을 의미한다. 캐시에 데이터를 얼마나 오랫동안 보관할 지 제어한다. 기본값: 5분
const { data, isPending, isError, error } = useQuery({
queryKey: ['events'],
queryFn: fetchEvents,
gcTime: 30000,
});
gcTime을 설정하면 캐시된 데이터가 설정한 시간만큼 보관된 후 폐기된다.
쿼리 함수에 데이터 전달
만약 상세 데이터를 가져오거나, 검색어를 입력받아서 백엔드에 데이터를 추가로 전달해야 할 때 쿼리 함수에서 매개변수를 받아 요청을 전송할 수 있다.
const { data, isPending, isError, error } = useQuery({
queryKey: ['events', { search: searchTerm }],
queryFn: () => fetchEvents(searchTerm),
});
쿼리 키에는 위에서 사용했던 'events'로만 구성하면 안된다. 모든 결과가 아닌 일부 결과만이 필요하기 때문에 쿼리 키에 동적인 값으로 전달할 수 있다.
export async function fetchEvents(searchTerm) {
let url = 'http://localhost:3000/events';
if (searchTerm) {
url += '?search=' + searchTerm;
}
const response = await fetch(url);
if (!response.ok) {
const error = new Error('An error occurred while fetching the events');
error.code = response.status;
error.info = await response.json();
throw error;
}
const { events } = await response.json();
return events;
}
여기서 searchTerm 매개변수를 확인해 백엔드 url을 동적으로 구성할 수 있어서 fetchEvents 함수를 모든 데이터를 가져오거나, 특정 검색 데이터를 가져오는 데 재사용할 수 있다.
하지만 리액트 쿼리는 쿼리 함수에 기본 데이터를 전달한다. 따라서 searchTerm이 없는 모든 데이터를 불러오는 상황에서 searchTerm에 객체가 들어가게 된다.
쿼리 함수에 전달되는 기본 데이터란, 쿼리에 사용된 키와 signal에 대한 정보를 가지고 있는 객체이다.
여기서 signal은 예를 들어 요청이 완료되기 전에 사용자가 페이지에서 나가는 경우 리액트가 자동으로 요청을 취소할 수 있는 용도로 사용한다.
따라서 쿼리 함수로 정의한 fetchEvents를 다음과 같이 수정할 수 있다.
export async function fetchEvents({ signal, searchTerm }) {
let url = 'http://localhost:3000/events';
if (searchTerm) {
url += '?search=' + searchTerm;
}
const response = await fetch(url, { signal: signal });
if (!response.ok) {
const error = new Error('An error occurred while fetching the events');
error.code = response.status;
error.info = await response.json();
throw error;
}
const { events } = await response.json();
return events;
}
매개변수로 리액트 쿼리가 제공하는 signal과, 우리가 필요로 하는 serachTerm을 받고 signal을 사용해 fetch함수의 두 번째 인수에 객체를 설정해 추가한다. 그러면 브라우저는 내부에서 취소 신호를 받아 요청을 중지할 수 있게 된다.
const { data, isPending, isError, error } = useQuery({
queryKey: ['events', { search: searchTerm }],
queryFn: ({ signal }) => fetchEvents({ signal, searchTerm }),
});
쿼리 함수를 정의할 때도 signal을 받고, fetchEvent 인수에 넣은 객체에 리액트 쿼리로 부터 받은 signal과 serachTerm을 설정하면 된다.
이때 쿼리 키로 설정할 때에도 searchTerm을 사용하고, fetchEvents에도 searchTerm을 전달하고 있다. 코드의 중복을 피하기 위해 다음과 같이 쿼리 키만 설정하고 쿼리함수에서 그 쿼리 키를 받아와 재사용할 수 있다.
const { data, isPending, isError, error } = useQuery({
queryKey: ['events', { searchTerm: searchTerm }],
queryFn: ({ signal, queryKey }) => fetchEvents({ signal, ...queryKey[1] }),
});
쿼리 활성화, 비활성화
useQuery에 전달하는 객체에 enabled 프로퍼티를 사용해 쿼리를 비활성화 할 수 있다. enabled의 기본값은 true이다.
enabled의 값이 false이면 쿼리가 비활성화되고 요청이 전송되지 않는다.
const { data, isLoading, isError, error } = useQuery({
queryKey: ['events', { search: searchTerm }],
queryFn: ({ signal }) => fetchEvents({ signal, searchTerm }),
enabled: searchTerm !== undefined,
});
'React' 카테고리의 다른 글
[React] Testing (0) | 2023.10.03 |
---|---|
[React] TanStack Query - 데이터 전송하기, Optimistic Update (0) | 2023.10.01 |
[React] lazy loading (0) | 2023.09.28 |
[React] react-router-dom의 defer (0) | 2023.09.27 |
[React] useFetcher (0) | 2023.09.27 |