본문 바로가기
2024~/React

[리액트] TanStack Query (구 React Query)

by Or0i쿠 2025. 6. 15.

TanStack Query (구 React Query)

웹 애플리케이션에서 서버 상태(Server State)를 관리하기 위한 강력한 라이브러리

더보기

과거에는 React Query라는 이름으로 React 생태계에서 매우 유명했지만, 현재는 React뿐만 아니라 Vue, Solid, Svelte 등 다양한 프레임워크를 지원하며 TanStack Query로 이름이 변경되었다.

  • 로딩 상태 관리: 데이터를 가져오는 동안 로딩 스피너 등을 보여줘야 합니다.
  • 에러 처리: API 호출 실패 시 에러 메시지를 보여주거나 재시도를 해야 합니다.
  • 캐싱: 한번 가져온 데이터를 캐시하여 불필요한 중복 요청을 줄이고 성능을 향상시켜야 합니다.
  • 데이터 신선도 관리: 캐시된 데이터가 최신 상태인지 확인하고, 필요하다면 백그라운드에서 자동으로 업데이트해야 합니다.
  • 데이터 동기화: 여러 컴포넌트에서 동일한 데이터를 사용할 때, 한 곳에서 데이터가 변경되면 다른 곳에서도 최신 데이터를 볼 수 있도록 동기화해야 합니다.
  • 무한 스크롤/페이지네이션: 대량의 데이터를 효율적으로 분할하여 가져와야 합니다.
  • 요청 중복 제거: 여러 컴포넌트가 동시에 동일한 데이터를 요청할 때, 실제 API 호출은 한 번만 이루어지도록 해야 합니다.

주요 기능 및 장점:

  • 자동 캐싱: 가져온 데이터를 자동으로 캐시하고 관리합니다.
  • 백그라운드 업데이트 (Stale-while-revalidate 패턴): 캐시된 데이터를 즉시 보여주고, 백그라운드에서 최신 데이터를 다시 가져와 업데이트합니다. 이를 통해 사용자에게 빠른 응답성을 제공합니다.
  • 자동 재시도 (Retry): API 호출 실패 시 설정된 횟수만큼 자동으로 재시도합니다.
    로딩, 에러, 성공 상태 관리: 훅을 통해 데이터의 현재 상태를 쉽게 파악하고 UI에 반영할 수 있습니다.
  • 요청 중복 제거 (Deduplication): 동일한 queryKey로 여러 곳에서 동시에 데이터를 요청해도 실제 API 호출은 한 번만 이루어집니다.
  • Optimistic Updates (뮤테이션 시): 데이터 변경(Mutation) 요청 시, 서버 응답을 기다리지 않고 미리 UI를 업데이트하여 사용자 경험을 향상시킵니다.
  • 무한 스크롤 및 페이지네이션 지원: useInfiniteQuery 훅 등을 통해 무한 스크롤이나 페이지네이션 구현을 쉽게 할 수 있습니다.
  • Devtools 제공: TanStack Query Devtools를 통해 캐시 상태, 쿼리 실행 과정 등을 시각적으로 확인하며 디버깅할 수 있습니다. 

핵심 개념:
Queries (useQuery, useInfiniteQuery): 

  • 서버로부터 데이터를 조회할 때 사용.
  • queryKey와 queryFn이 핵심

Mutations (useMutation): 

  • 서버의 데이터를 변경할 때 사용 (생성, 업데이트, 삭제 등)
  • mutationFn과 onSuccess, onError 등의 콜백이 핵심

QueryClient: 

  • TanStack Query의 모든 캐시와 설정을 관리하는 중앙 인스턴스
  • 애플리케이션 루트에 한 번 설정함

Query Keys: 캐시된 데이터를 식별하는 고유한 키

 

1. Queries (useQuery, useInfiniteQuery)

  • 목적: 서버로부터 데이터를 **조회(Fetching)**할 때 사용합니다. 주로 HTTP GET 요청과 같은 읽기 작업에 해당됩니다.
  • 핵심:
    • queryKey: 조회하려는 데이터의 고유한 식별자입니다. 배열 형태로 정의하며, TanStack Query가 캐싱, 무효화, 공유 등을 관리하는 기준이 됩니다. queryKey가 변경되면 TanStack Query는 해당 데이터를 새로 가져옵니다.
    • queryFn: 실제로 데이터를 가져오는 비동기 함수입니다. 이 함수는 반드시 Promise를 반환해야 합니다. useQuery는 이 함수를 호출하여 데이터를 얻습니다.
  • 반환 값: 쿼리의 현재 상태(isLoading, isError, isFetching, status), 가져온 데이터(data), 발생한 에러(error) 등을 담은 객체를 반환합니다.
  • useQuery: 단일 데이터를 가져올 때 사용합니다.
  • useInfiniteQuery: 페이지네이션이나 무한 스크롤과 같이 여러 페이지에 걸쳐 데이터를 가져올 때 사용합니다. queryFn 외에 initialPageParam, getNextPageParam, getPreviousPageParam 등의 옵션을 추가로 사용합니다.

예시:

typescript// 단일 사용자 정보 조회
const { data: user, isLoading } = useQuery({
  queryKey: ['user', userId], // 사용자 ID에 따라 캐시 구분
  queryFn: () => fetchUserById(userId), // userId를 사용하여 API 호출
});

// 게시글 목록 무한 스크롤 조회
const { data: posts, fetchNextPage } = useInfiniteQuery({
  queryKey: ['posts'],
  queryFn: ({ pageParam = 0 }) => fetchPosts(pageParam), // pageParam을 사용하여 다음 페이지 데이터 요청
  initialPageParam: 0,
  getNextPageParam: (lastPage, allPages) => lastPage.nextPage, // 다음 페이지 번호 계산
});

2. Mutations (useMutation)

  • 목적: 서버의 데이터를 **변경(Modifying)**할 때 사용합니다. 주로 HTTP POST, PUT, PATCH, DELETE 요청과 같은 쓰기 작업에 해당됩니다.
  • 핵심:
    • mutationFn: 실제로 데이터를 변경하는 비동기 함수입니다. useMutation 훅이 반환하는 mutate 함수가 호출될 때 전달된 인자를 받아서 서버에 요청을 보냅니다. Promise를 반환해야 합니다.
    • onSuccess, onError, onSettled 등의 콜백: 뮤테이션 작업의 결과에 따라 실행되는 함수들입니다. 성공 시 onSuccess, 실패 시 onError, 성공/실패와 상관없이 완료 시 onSettled가 실행됩니다. 이 콜백들에서 캐시 무효화, UI 업데이트 등의 후속 작업을 수행합니다.
  • 반환 값: 뮤테이션의 현재 상태(isLoading, isError, isSuccess, status), 뮤테이션 결과 데이터(data), 발생한 에러(error), 그리고 뮤테이션을 실행하는 함수(mutate) 등을 담은 객체를 반환합니다.

예시:

typescriptimport { useMutation, useQueryClient } from '@tanstack/react-query';

const queryClient = useQueryClient();

// 게시글 생성 뮤테이션
const createPostMutation = useMutation({
  mutationFn: (newPostData) => createPost(newPostData), // 새 게시글 데이터를 받아 API 호출
  onSuccess: () => {
    // 게시글 생성 성공 시, 게시글 목록 쿼리 캐시 무효화
    queryClient.invalidateQueries({ queryKey: ['posts'] });
    console.log('게시글 생성 성공!');
  },
  onError: (error) => {
    console.error('게시글 생성 실패:', error);
  },
});

// 컴포넌트에서 뮤테이션 실행
function NewPostForm() {
  const handleSubmit = (formData) => {
    createPostMutation.mutate(formData); // 뮤테이션 실행
  };

  // ... 로딩 상태에 따른 UI (예: 버튼 비활성화) 처리
  if (createPostMutation.isLoading) {
    return <button disabled>생성 중...</button>;
  }

  return (
    <form onSubmit={handleSubmit}>
      {/* ... 폼 입력 필드 ... */}
      <button type="submit">게시글 생성</button>
    </form>
  );
}

3. QueryClient

  • 목적: TanStack Query의 모든 캐시, 쿼리 인스턴스, 뮤테이션 인스턴스, 설정 등을 관리하는 중앙 관리자입니다.
  • 역할:
    • 캐시된 데이터를 저장하고 업데이트합니다.
    • queryKey를 기반으로 쿼리들을 식별하고 관리합니다.
    • invalidateQueries 메서드를 사용하여 특정 queryKey를 가진 쿼리의 캐시를 무효화하고 데이터를 새로 가져오도록 지시합니다.
    • setQueryData, getQueryData 등의 메서드를 사용하여 캐시 데이터를 직접 조작할 수 있습니다.
  • 설정: 애플리케이션의 최상위 컴포넌트에서 QueryClientProvider를 사용하여 QueryClient 인스턴스를 제공해야 합니다.
  • 사용: 컴포넌트나 훅에서는 useQueryClient 훅을 사용하여 QueryClient 인스턴스에 접근합니다.

예시:

typescript// App.tsx 또는 index.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

// ✨ QueryClient 인스턴스 생성
const queryClient = new QueryClient({
  // 기본 옵션 설정 (선택 사항)
  defaultOptions: {
    queries: {
      staleTime: 1000 * 60 * 5, // 5분 동안 데이터 신선하게 유지
    },
  },
});

function App() {
  return (
    // ✨ QueryClientProvider로 애플리케이션 감싸기
    <QueryClientProvider client={queryClient}>
      {/* ... 애플리케이션 컴포넌트들 ... */}
      <UsersList />
      <NewPostForm />
      {/* ✨ Devtools 추가 (개발 환경에서만) */}
      {process.env.NODE_ENV === 'development' && <ReactQueryDevtools initialIsOpen={false} />}
    </QueryClientProvider>
  );
}

4. Query Keys

  • 목적: 캐시된 데이터를 고유하게 식별하는 문자열 또는 배열입니다.
  • 규칙:
    • 항상 배열 형태여야 합니다.
    • 배열의 첫 번째 요소는 데이터의 종류를 나타내는 문자열입니다.
    • 그 뒤에는 데이터를 특정하는 데 필요한 모든 변수들을 순서대로 포함시켜야 합니다. 원시 값(문자열, 숫자, 불리언, null)과 객체를 포함할 수 있습니다. 객체의 순서는 중요하지 않습니다.
  • 중요성: TanStack Query는 queryKey를 기반으로 캐시를 관리하고, 동일한 키를 가진 쿼리들을 연결하며, invalidateQueries 등의 작업 대상을 결정합니다. queryKey가 변경되면 TanStack Query는 이를 새로운 데이터로 간주하고 다시 fetching합니다.

예시:

typescript['todos'] // 모든 할 일 목록
['todo', 5] // ID가 5인 할 일
['todos', { status: 'done', priority: 'high' }] // 특정 상태와 우선순위를 가진 할 일 목록
['users', userId, 'projects'] // 특정 사용자의 프로젝트 목록