1. 클라이언트 상태 vs 서버 상태
리액트에서 상태는 크게 두 가지로 나뉩니다.
구분 설명 예시
| 클라이언트 상태(Client State) | 컴포넌트 내부에서만 사용하는 값 | 모달 열림 여부, 입력 값 |
| 서버 상태(Server State) | 서버로부터 가져온 데이터 | 게시글 목록, 사용자 정보 |
useState나 useReducer는 클라이언트 상태 관리에는 적합하지만,
서버 상태 관리에는 한계가 있습니다.
왜냐하면 서버 데이터는 비동기적이며, 여러 소스에서 동시에 바뀔 수 있기 때문입니다.
이를 효율적으로 관리하기 위해 등장한 라이브러리가 바로 React Query입니다.
2. React Query란?
React Query는 서버 데이터 관리에 특화된 라이브러리입니다.
비동기 데이터 fetching, 캐싱, 리페칭(refetch), 동기화, 에러 처리까지
모든 과정을 자동으로 처리해줍니다.
간단히 말해,
“서버 상태를 관리하는 Redux”
라고 할 수 있습니다.
3. 설치 및 기본 세팅
npm install @tanstack/react-query
프로젝트 루트에서 QueryClientProvider로 앱을 감쌉니다.
// main.jsx 또는 App.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import App from './App';
const queryClient = new QueryClient();
ReactDOM.createRoot(document.getElementById('root')).render(
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
);
이제 어디서든 React Query 훅(useQuery, useMutation 등)을 사용할 수 있습니다.
4. useQuery – 데이터 조회 (Fetching)
가장 기본적인 훅입니다.
비동기 요청을 보내고, 로딩/에러/성공 상태를 자동으로 관리합니다.
import { useQuery } from '@tanstack/react-query';
import axios from 'axios';
function Users() {
const { data, isLoading, isError } = useQuery({
queryKey: ['users'],
queryFn: async () => {
const res = await axios.get('https://jsonplaceholder.typicode.com/users');
return res.data;
},
});
if (isLoading) return
로딩 중...
;
if (isError) return
에러 발생!
;
return (
- {data.map((user) => (
- {user.name} ))}
);
}
- queryKey: 데이터를 구분하는 고유 키
- queryFn: 데이터를 가져오는 함수
React Query는 이 키를 기준으로 데이터를 캐싱합니다.
즉, 같은 키로 다시 요청하면 네트워크 요청 없이 캐시 데이터를 반환합니다.
5. useMutation – 데이터 변경 (POST, PUT, DELETE)
데이터를 추가하거나 수정할 때는 useMutation을 사용합니다.
import { useMutation, useQueryClient } from '@tanstack/react-query';
import axios from 'axios';
function CreateUser() {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: (newUser) => axios.post('/api/users', newUser),
onSuccess: () => {
queryClient.invalidateQueries(['users']); // 기존 데이터 갱신
},
});
const handleSubmit = () => {
mutation.mutate({ name: '홍길동', email: 'hong@example.com' });
};
return (
<button onClick={handleSubmit}>
{mutation.isLoading ? '등록 중...' : '사용자 추가'}
</button>
);
}
- mutationFn: 실제 서버 요청을 수행하는 함수
- onSuccess: 요청이 성공했을 때 실행되는 콜백 (주로 캐시 무효화용)
6. 자동 리페칭(Refetch)
React Query는 페이지를 다시 포커싱하거나 네트워크가 재연결될 때
자동으로 데이터를 갱신해줍니다.
const { data } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
refetchOnWindowFocus: true,
refetchOnReconnect: true,
});
이 덕분에 사용자는 항상 최신 데이터를 볼 수 있습니다.
7. 캐시와 스테일 타임
React Query는 데이터를 캐싱하고,
지정된 시간(staleTime) 동안은 서버 재요청을 하지 않습니다.
useQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
staleTime: 1000 * 60 * 5, // 5분 동안 캐시 유지
});
- staleTime: 데이터를 신선하다고 간주하는 시간
- cacheTime: 캐시가 메모리에 유지되는 시간
이를 적절히 조정하면 네트워크 부하를 크게 줄일 수 있습니다.
8. 병렬 쿼리와 의존 쿼리
React Query는 여러 쿼리를 병렬로 처리하거나,
특정 쿼리가 끝난 후에 다른 쿼리를 실행하는 것도 가능합니다.
(1) 병렬 쿼리
const usersQuery = useQuery({ queryKey: ['users'], queryFn: fetchUsers });
const postsQuery = useQuery({ queryKey: ['posts'], queryFn: fetchPosts });
(2) 의존 쿼리
const { data: user } = useQuery(['user', userId], () => fetchUser(userId));
const { data: posts } = useQuery(['posts', user?.id], () => fetchPosts(user.id), {
enabled: !!user, // user가 있을 때만 실행
});
9. React Query Devtools
React Query는 Devtools를 제공해 캐시와 상태를 시각적으로 확인할 수 있습니다.
npm install @tanstack/react-query-devtools
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
<QueryClientProvider client={queryClient}>
<App />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
이 도구를 사용하면 어떤 쿼리가 캐싱 중인지,
언제 리페칭이 일어나는지를 실시간으로 확인할 수 있습니다.
10. 마무리
React Query는 서버 데이터 관리의 복잡성을 획기적으로 줄여주는 도구입니다.
핵심 요약:
- 서버 상태를 자동으로 캐싱하고 최신화
- 비동기 요청의 로딩/에러 상태를 내장 관리
- 데이터 무효화(invalidateQueries)로 손쉬운 갱신
- Devtools로 캐시 시각화
기존 Redux나 Recoil보다 데이터 요청 로직을 훨씬 단순하게 유지할 수 있습니다.
다음 강의에서는 React Query를 실전 프로젝트에 적용하여
서버 데이터와 전역 상태를 조합해 완성도 높은 대시보드를 만드는 방법을 살펴보겠습니다.