프론트엔드/React

[React] 서버에서 상태를 가져온다고? React-Query

s_omi 2025. 3. 30. 09:26

Recoil, Redux, Zustand와 같은 상태 관리 라이브러리를 두고 왜 React Query를 사용하는 것일까?

React Query를 사용하는 이유는, 서버 상태를 효율적이고 일관되게 관리하기 위해서이다.

 

React 자체는 클라이언트 상태를 다루는 데는 강력하지만,

서버에서 데이터를 가져오고 이를 유지, 갱신, 캐싱하는 등의 기능은 기본적으로 제공하지 않기 때문에 별도의 도구가 필요하다.

 

React Query는 이러한 서버 상태를 자동으로 관리해 주는 훌륭한 라이브러리이며,

React Query를 사용하면 데이터를 요청할 때마다 수동으로 로딩 상태, 에러 처리, 데이터 갱신 등을 구현하지 않아도 된다.

 

내부적으로 자동 캐싱, 요청 중복 제거, 실패 시 재시도, 포커스 복귀 시 자동 리페치 등의 기능을 제공하여

개발자가 비즈니스 로직에 집중할 수 있게 도와줍니다.

 

또한, 상태 관리 라이브러리(Redux, Zustand 등)처럼 직접 전역 상태를 설계하지 않아도 되고,

API 호출과 관련된 모든 과정을 간단한 훅으로 처리할 수 있기 때문에 코드가 간결해지고 유지보수가 쉽다.

 

쉽게 말해, 비동기 데이터 처리에 따르는 복잡성을 줄이고,

서버와 클라이언트 간의 데이터 동기화를 더 스마트하고 자동화된 방식으로 관리해 주기 때문에 널리 사용되고 있다.

 

 

React-Query 

1. 사용 설정

먼저 설치는 다음과 같이 하면 된다.

npm install react-query
# 또는
yarn add react-query

 

React Query에서는 QueryClient를 생성하고, QueryClientProvider로 감싸야 한다.

 

 

import React from 'react';
import ReactDOM from 'react-dom';
import { QueryClient, QueryClientProvider } from 'react-query';
import App from './App';

const queryClient = new QueryClient();

ReactDOM.render(
  <React.StrictMode>
    {/* 모든 자식 컴포넌트에서 React Query를 사용할 수 있도록 Provider로 감싸기 */}
    <QueryClientProvider client={queryClient}>
      <App />
    </QueryClientProvider>
  </React.StrictMode>,
  document.getElementById('root')
);

 

이때 QueryClient는 React Query의 핵심 객체로 캐시/요청을 관리하며,

 

QueryClientProvider는 React Query를 프로젝트 전역에서 사용할 수 있게 해주는 Context Provider이다.

 

 

 

2. 핵심 기능

  • 동일한 쿼리 키에 대한 요청이 반복될 경우, 캐시된 데이터를 먼저 반환한다.
  • 포커스(브라우저 탭) 복귀 시 또는 네트워크 재연결 시, 쿼리가 오래되었을 때 자동 새 데이터 가져온다.
  • Stale Time / Cache Time
    • Stale Time: 데이터가 신선하다고 간주되는 시간
    • Cache Time: 안 쓰는 쿼리에 대해 캐시를 유지하는 시간
  • 요청이 실패하면 retry 옵션으로 자동으로 재시도할 수 있다.
  • enabled, onSuccess, onError, refetchOnWindowFocus, refetchInterval 등 옵션 기반으로 세밀한 제어가 가능하다.

 

 

3. 메소드

  • useQuery(): 서버에서 데이터 가져오고 캐시 (GET)
    • 내부적으로 isLoading, isError, data, refetch 등을 제공
const { data, isLoading, error, refetch } = useQuery(
  ['todos'],
  () => axios.get('/api/todos').then(res => res.data)
);
  • useMutation(): 서버에 데이터 변경 (POST, PUT, DELETE)
    • 성공/실패 콜백, 캐시 무효화와 조합해서 자주 사용
const mutation = useMutation(
  (newTodo) => axios.post('/api/todos', newTodo),
  {
    onSuccess: () => {
      queryClient.invalidateQueries(['todos']); // 목록 다시 불러오기
    }
  }
);

mutation.mutate({ title: '할 일 추가' });
  • useQueryClient(): 캐시나 쿼리 제어(무효화, 갱신 등)를 위한 클라이언트 인스턴스 접근해 직접 조작
    • QueryClient 인스턴스를 가져와서 캐시/쿼리 제어할 때 사용
    • invalidateQueries(): 특정 쿼리 키의 데이터를 무효화 (다시 요청하게 함)
    • setQueryData(): 특정 쿼리 키의 캐시 데이터를 수동으로 설정
    • getQueryData(): 특정 쿼리 키의 캐시 데이터를 가져옴
    • removeQueries(): 캐시된 쿼리 제거
const queryClient = useQueryClient();
queryClient.invalidateQueries(['todos']); // 쿼리 무효화
queryClient.setQueryData(['user'], { name: '홍길동' }); // 캐시 수동 설정
  • useInfiniteQuery(): 무한 스크롤 / 페이지네이션 등 페이지 기반 데이터 로딩
    • getNextPageParam을 통해 다음 페이지 정보 관리
const { data, fetchNextPage, hasNextPage, isFetchingNextPage 
} = useInfiniteQuery(
  ['posts'],
  ({ pageParam = 1 }) => axios.get(`/api/posts?page=${pageParam}`).then(res => res.data),
  {
    getNextPageParam: (lastPage, allPages) => {
      return lastPage.hasMore ? allPages.length + 1 : undefined;
    },
  }
);
  • useIsFetching(): 쿼리들이 fetching 중인지 확인하는 전역 로딩 감지
const isFetching = useIsFetching();
  • useIsMutating(): 현재 mutation 작업이 진행 중인지 확인
const isMutating = useIsMutating();

 

 

 

4. React Query Devtools

개발 편의를 위해, React Query Devtools를 추가로 설치하면 편리합니다.

npm install react-query-devtools
import { QueryClientProvider } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools';
// ...
<QueryClientProvider client={queryClient}>
  <App />
  <ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>

 

 

이렇게 하면 Devtools 패널에서 쿼리 상태, 캐시 데이터, 오류 등을 실시간으로 확인이 가능하다.