[Toy Project 기록하기 9] React-Query로 서버 상태 관리하기
리액트에서 state는 떼려야 뗄 수 없는 것으로 state의 상태 관리를 위한 다양한 라이브러리가 존재합니다. 지난번 Client-side에서는 프로젝트의 클라이언트의 복잡도가 높지 않고, 공통으로 사용하는 전역 상태의 데이터도 많지 않아 리액트의 context를 통해 로그인한 유저의 데이터를 저장했습니다. 그렇다면 Server-side에서 가져오는 데이터를 위한 상태 관리도 필요할 텐데요. 오늘은 제 프로젝트에서 Server-side에서 state 상태 관리를 위해 도입한 React-Query에 대해 알아보고 활용한 방법에 대해 적어보겠습니다.
Server State를 관리하며 발생할 수 있는 문제
원격 데이터는 서버에서 관리되는 데이터를 말합니다. 데이터를 가져오고 업데이트하기 위해서 비동기 API가 필요합니다. 그런데 해당 데이터가 클라이언트가 모르게 원격에서 변경되면 응답은 구식(out-of-date)이 될 수 있습니다.
React-Query란
서버의 값을 클라이언트에 가져오거나 캐싱, 업데이트, 에러핸들링 등 비동기 로직을 지원하는 data fetching 라이브러리입니다.
React-Query가 제공하는 다음과 같은 기능을 통해 Server State를 더욱 효율적으로 관리할 수 있습니다.
- 데이터 캐싱
- 데이터를 캐시에서 유지할 시간
- 리프레시 간격
- 서버 데이터 중복 호출 제거
- 페이지네이션, 레이지 로딩 데이터의 성능 최적화
- 쿼리 결과에 대한 성공, 로딩, 에러 콜백
React-Query의 라이프 사이클
fetching: 요청 중인 데이터
fresh: 신선한 데이터(↔ stale한 데이터). 컴포넌트가 마운트, 업데이트되어도 데이터를 다시 요청하지 않습니다.
stale: 만료된 데이터. 컴포넌트가 마운트, 업데이트 되면 데이터를 다시 요청합니다.
inactive: 사용하지 않는 데이터로 일정 시간이 지나면 가비지 컬렉터가 캐시에서 제거합니다.
delete: 가비지 컬렉터에 의해 캐시에서 제거된 상태를 말합니다.
➡️ 기본적으로 데이터를 fetching 해온 후 데이터를 캐싱하며 백그라운드에서 서버에 주기적으로 polling을 하여 데이터가 유효한지 검사하고 유효하지 않으면(stale) 업데이트해 데이터를 동기화합니다.
React-Query 사용하기
react-query 설치하기
npm i react-query
QueryClientProvider
React Query는 캐시를 관리하기 위해 QueryClient 인스턴스를 사용합니다. 그리고 QueryClientProvider로 최상단에서 감싸줍니다.
React Query DevTools는 쿼리 상태를 이해하는 데 큰 도움을 줍니다. 또한 현재 캐시에 있는 데이터를 알려주기 때문에 디버깅을 쉽게 할 수 있습니다.
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
import { QueryClient, QueryClientProvider } from 'react-query';
import { UserProvider } from './context/UserContext';
import { ReactQueryDevtools } from 'react-query/devtools';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
const queryClient = new QueryClient({
defaultOptions: {
queries: {
// 실패 시 retry하는 횟수 (default: 3)
retry: 1,
// 윈도우 창이 포커스 될 때마다 refetch (default: true)
refetchOnWindowFocus: false,
},
},
});
root.render(
<QueryClientProvider client={queryClient}>
// 데이터의 상태를 확인할 수 있는 개발자 도구
<ReactQueryDevtools initialIsOpen={true} />
<BrowserRouter>
<UserProvider>
<App />
</UserProvider>
</BrowserRouter>
</QueryClientProvider>
);
React Query는 API 요청이 Query와 Mutation으로 나누어져 있습니다.
useQuery
서버에서 데이터를 가져올 때 사용하는 요청입니다.
- 첫 번째 인자로 Unique Key를 받습니다. 해당 Key를 통해 다른 곳에서도 데이터를 꺼내올 수 있습니다.
// FeedDetail.tsx
import { useQuery } from 'react-query';
import { getFeed } from '../services/feed';
const { isLoading, isError, data, error } = useQuery(
['getFeed', _id], // queryKey: 데이터를 캐시할 때 사용하는 Unique Key
() => getFeed({ _id }) // fetchFn: Promise를 반환하는 함수
);
if (isLoading) {
return <Loading />;
}
if (isError && isAxiosError(error)) {
return <NotFound />;
}
if (!isLoading && !data?.data) {
return <NotFound />;
}
// ...
useMutation
서버의 데이터 변경(생성, 수정, 삭제)을 요청할 때 사용합니다.
// FeedDetail.tsx
import { toggleLikeFeed } from '../services/feed';
const queryClient = useQueryClient();
const { mutate: likeFeedMutation } = useMutation(
// mutation 요청을 수행하기 위한 Promise를 반환하는 함수
toggleLikeFeed, {
// 성공 시 실행
onSuccess: () => {
// Mutation이 성공하면 쿼리를 refetch 함
queryClient.invalidateQueries(['getFeed', _id]);
},
// 에러 발생 시 실행
onError: (error: TError) => {
if (error.response.status === 401) {
alert('로그인 후 이용해주세요.');
}
setIsLiked((prev) => !prev);
},
});
React Query에는 이 밖에도 많은 옵션과 다양한 기능들이 있습니다. React Query 공식 홈페이지에서 필요한 상황에 따라 알맞은 기능을 찾아보고 적용하는 것이 좋을 것 같습니다. 참고로 React Query의 useInfiniteQuery는 무한 스크롤을 간편하게 만들 수 있는 기능이며 다음 포스팅에서 다루겠습니다!
🧗♀️ 제가 진행한 프로젝트가 궁금하다면
🔽 프론트엔드는 이곳에서 확인하실 수 있습니다.
https://github.com/Team-Madstone/doljabee-fe
GitHub - Team-Madstone/doljabee-fe: Climbing Community
Climbing Community. Contribute to Team-Madstone/doljabee-fe development by creating an account on GitHub.
github.com
🔽 백엔드는 이곳에서 확인하실 수 있습니다.
https://github.com/Team-Madstone/doljabee-be
GitHub - Team-Madstone/doljabee-be: Climbing Community
Climbing Community. Contribute to Team-Madstone/doljabee-be development by creating an account on GitHub.
github.com
참고 자료
https://tanstack.com/query/v3/