[Tanstack Query] 기본 개념

Woonil·2025년 11월 4일
0

리액트 쿼리

목록 보기
1/6
post-thumbnail

React 애플리케이션의 상태는 관리하는 데이터의 특성에 따라 지역 상태(Local State)와 전역 상태(Global State)로 나눌 수 있다.

  • 지역 상태(Local State): 특정 <input>의 입력값 등
  • 전역 상태(Global State): 앱의 테마 데이터, 세션 정보 데이터 등

한편, 프론트엔드에서는 비동기 API 요청으로부터 얻은 데이터에 관한 정보도 상태로 관리할 일이 많다. React 개발에서 데이터 페칭 후 데이터를 화면에 표시하기까지의 과정을 생각해보자. 사용자에게 매끄러운 화면 동작을 제공하기 위해서, useEffect 훅에서 수행한 API 요청으로 얻은 데이터로부터 로딩 상태 처리/성공 및 실패 유무에 따른 UI 표시/에러 처리/캐싱과 같은 작업을 진행해야 할 것이다. 이를 위해서 결과에 따라 조건부 JSX 렌더링을 해주어야 하며 캐싱을 위한 작업을 별도로 수행해야 한다.

매번 이런 동일한 패턴의 작업을 수행하기에는 코드의 중복, 상태 관리의 복잡성, 관심사 분리 등 개발자가 신경써야 할 부분이 많이 생겨날 듯하다. 이때 이러한 상태들을 쉽게 관리해 줄 필요가 생겼고, ‘서버 상태(Server State)’라는 개념이 등장했다.

Tanstack Query(구 리액트 쿼리)는 React 진영의 대표적인 서버 상태 관리 라이브러리로, 웹 애플리케이션의 서버에서 가져온 데이터 작업을 용이하게 한다.

  • 장점
    • 서버 측 데이터(서버 상태)와 클라이언트 측 데이터(클라이언트 상태)를 개별적으로 처리하는 데 도움을 준다.
    • 실시간 데이터 업데이트 처리, 서버 상태 관리, 복잡한 앱의 렌더링 최적화와 같은 작업을 단순화한다.
  • 설치
    npm i @tanstack/react-query
    # 관리되는 캐시를 쉽게 확인할 수 있게 해주는 개발 편의툴
    npm i @tanstack/react-query-devtools
  • Query Client & Query Client Provider: Query Client는 일종의 서버 상태의 저장소로, 이를 통해 캐시와 상호작용이 가능해진다. defaultOptions 설정 시 모든 query와 mutation에 기본 옵션을 추가한다. 다양한 메서드를 가지고 있어 상황에 맞게 캐시를 조작할 수 있다. <QueryClientProvider>는 QueryClient를 애플리케이션에 연결하고 제공하는 역할을 한다. 최상단에 감싼 후, 생성한 QueryClient 인스턴스를 client prop에 주입한다.
    import { RouterProvider } from "react-router-dom";
    import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
    import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
    import router from "./router";
    
    const queryClient = new QueryClient({
      defaultOptions: {
        queries: {
          refetchOnWindowFocus: false,
        },
      },
    });
    
    export default function App() {
    
      return (
        <QueryClientProvider client={queryClient}>
          <RouterProvider router={router} />
          <ReactQueryDevtools />
        </QueryClientProvider>
      );
    }
  • Query & Mutation: query는 데이터 조회하여 query key를 바탕으로 캐싱을 진행하고, mutation은 데이터 가공하며 캐싱을 진행하지 않고 서버 측에 side effect를 일으킨다.

🤔개념

클라이언트 상태 vs 서버 상태

  • Client State: 어플리케이션에서만 필요한 데이터 ex) 모달 오픈 여부, 테마, 언어 등

  • Server State: 서버에서 받아오는 데이터 ex) DB 상품 정보

  • 특징
    • 직접 관리하거나 소유하지 않는 원격 저장소에 보관된다.
    • fetching과 updating을 위해서 비동기 api 요청이 필요하다.
    • 여러 사용자가 공유하는 데이터로, 자신이 모르는 사이에 다른 사람에 의해 변경될 수도 있다.
    • 주의하지 않으면 사용 중인 데이터의 상태가 최신으로 유지되지 않을 수 있다.

캐싱 메커니즘

상태설명
fetching최초로 데이터를 불러오는 중인 상태
fresh데이터가 신선한 상태
stale데이터가 상한 상태
inactive캐시 데이터가 현재 사용되지 않아 메모리에 기록된 상태
deleted가비지 컬렉팅에 의해 메모리 상에서도 삭제된 상태

refetching

특정 조건일 때 stale 상태의 데이터를 fresh 상태로 전이하기 위해 데이터를 다시 불러오는 행위인 refetching을 수행하게 설정 가능하다.

  • Mount: 특정 캐시 데이터를 사용하는 컴포넌트가 마운트 되었을 때

  • WindowFocus: 사용자가 특정 캐시 데이터를 사용하는 탭으로 다시 복귀했을 때

  • Reconnect: 인터넷 연결이 끊어졌다가 다시 연결되었을 때

  • Interval: 특정 시간을 주기로

stale time & gc time

데이터를 캐싱하고 관리하는 데 중요한 두 가지 설정값이다.

  • staleTime : 첫 데이터 페칭 후 얼마 동안 네트워크 요청 없이 캐시된 데이터를 사용할지 정하는 시간이다. ‘유통기한’을 떠올리면 이해하기 쉬울 것이다. (기본값: 0)

    stale은 ‘신선하지 않은, 탁한’이란 의미를 지닌 단어이다.

  • gcTime : 메모리 상에서 사용되지 않거나 inactive 상태의 캐시 데이터가 남아있는 시간이다. 즉, 캐시를 사용하는 쿼리를 수행하는 컴포넌트가 모두 언마운트된 이후 캐시 데이터를 메모리 상에 얼마 동안 유지할지 결정한다. 이 시간이 지나면 해당 캐시 데이터는 가비지 컬렉팅된다. (기본값: 5분, SSR 중에는 Infinity)

API

API 옵션은 queryClientdefaultOptions 로 전역적으로 설정 가능하나, 개별 쿼리에서 별도 옵션을 주어야 하는 경우 해당 쿼리에서만 전역 옵션을 덮어쓰기 할 수 있다.

refetchOnWindowFocus

사용자가 애플리케이션을 떠난 후 다시 돌아왔을때, 데이터가 stale(신선하지 않은)한 데이터가 존재한다면 리액트 쿼리는 백그라운드 상에서 자동적으로 fresh(새로운)데이터를 요청한다.

retry

useQuery 쿼리가 실패(쿼리 함수가 에러를 던질 경우)했을 때, 리액트 쿼리는 쿼리의 요청이 연속적인 재시도(default: 3회)의 한계점에 다다르기 직전까지 자동적으로 재시도를 수행한다.

쿼리 무효화

queryClient의 메서드로 쿼리를 무효화(stale 상태로 만듦)하고, 최신화(refetch: 새로운 데이터를 fetch)하는 기능이다. invalidateQueries에 옵션이 없는 경우에는 캐시 내의 모든 쿼리를 무효화한다. 만약, 옵션에 queryKey가 있으면 해당 키를 가진 모든 쿼리를 무효화한다.

import { createTodo } from "@/api/create-todo";
import { useMutation, useQueryClient } from "@tanstack/react-query";

export function useCreateTodoMutation() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: createTodo,
    onSuccess: () => {
      queryClient.invalidateQueries({
        queryKey: ["todos"],
      });
    },
  });
}

쿼리키 관리

쿼리 무효화 시, 동일한 쿼리키를 포함하는 다른 키까지 무효화되는 비효율이 생길 수 있다. 또한 쿼리키를 중복으로 정의하여 예기치 못한 에러를 발생시킬 여지가 있다.

Tanstack Query 공식문서에는 쿼리 무효화의 조건을 다음의 예시를 들어 설명한다.

// Invalidate every query in the cache
queryClient.invalidateQueries()
// Invalidate every query with a key that starts with `todos`
queryClient.invalidateQueries({ queryKey: ['todos'] })

아래와 같이 투두 리스트에 새로운 할 일을 추가하는 상황을 생각해보자. ['todos'] , ['todos', 'isNotDone'] 쿼리키에 대해 모두 refetching이 수행된다.

따라서 쿼리키를 중앙에서 관리하는 것이 권장되며, 이는 효율적인 쿼리키 관리를 도와준다.

// constants.ts
export const QUERY_KEYS = {
  todo: {
    all: ["todo"],
    list: ["todo", "list"],
    detail: (id: string) => ["todo", "detail", id],
  },
};
// 쿼리키 팩토리를 불러옴
import { QUERY_KEYS } from "@/lib/constants";

export function useTodosData() {
  return useQuery({
    queryKey: QUERY_KEYS.todo.all,
    queryFn: fetchTodos,
  });
}

다음과 같은 상황에서는 말이 달라진다. 상세 페이지로 이동했다가 다시 목록 페이지로 돌아오는 상황이다. ‘캐싱 메커니즘’에서 refetching이 수행되는 조건을 상기해보자. ['todos'] , ['todos', id] 의 쿼리키가 모두 ‘todos’로 시작되지만, ['todos', id] 의 값을 사용하는 컴포넌트(여기서는 상세 페이지)가 언마운트 되어 미사용 상태가 되었으므로 inactive 상태로 전환된다. 즉, stale 상태가 아니기 때문에 refetching이 일어나지 않은 것이다.

캐시 직접 조작

invalidateQueries 를 통해 refetching을 수행함으로써, 캐시를 업데이트할 수 있다고 했다. 유튜브의 무한 답글 기능이 지원되는 댓글창에 새로운 댓글을 추가하는 상황을 생각해보자. 새로운 댓글이 달릴 때마다 기존 댓글 중 마지막 댓글 이후에 추가된 등록한 댓글이 보여져야 하는 UI 요구사항이 주어진다. invalidateQueries 를 통해 모든 댓글 데이터를 다시 불러와야 할 것인가?

setQueryData 를 통해 캐시에 직접 접근하여 조작함으로써, 효율적인 데이터 업데이트를 수행할 수 있다. 이 방법을 사용하려면 반드시 서버는 데이터 업데이트 이후 수정된 데이터를 클라이언트에게 넘겨주어야 한다.

import { useQueryClient } from "@tanstack/react-query";

/**
 * CREATE: 할 일
 */
export function useCreateTodoMutation() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: createTodo,
    onSuccess: (newTodo) => {
      // 캐시 직접 수정
      queryClient.setQueryData<ITodo[]>(QUERY_KEYS.todo.list, (prevTodos) => {
        if (!prevTodos) return [newTodo];
        return [...prevTodos, newTodo];
      });
    },
  });
}

참고자료

한 입 크기로 잘라먹는 실전 프로젝트 SNS 편 - 이정환
TanStack Query | React Query, Solid Query, Svelte Query, Vue Query
[10분 테코톡] 시모의 TanStack Query
TanStack Query 강좌 #1 소개/설치/useQuery

profile
프론트 개발과 클라우드 환경에 관심이 많습니다:)

0개의 댓글