TanStack Query - キャッシング管理(かんり)とデータ変更(へんこう)

💾 キャッシングメカニズム 2 - Inactive と GC

Inactive 状態(じょうたい)

freshまたはstaleからinactiveに変(か)わる状況(じょうきょう)です。
このキャッシュデータを活用(かつよう)するコンポーネントがないときに発生(はっせい)します。

fresh 또는 stale에서 inactive로 변하는 상황입니다.
이 캐시 데이터를 활용하는 컴포넌트가 없을 때 발생합니다.

状態 전환:

fresh/stale (사용 중)
    ↓
  컴포넌트 언마운트
    ↓
inactive (사용되지 않음)

GC Time (Garbage Collection Time)

キャッシュデータが現在(げんざい)使用(しよう)されていない状態(じょうたい)です。
多(おお)すぎるとメモリ浪費(ろうひ)につながるため、gcTime後(ご)にdeletedされます。

캐시 데이터가 현재 사용되지 않는 상태입니다.
너무 많으면 메모리 낭비로 이어지므로 gcTime 후에 deleted됩니다.

src/hooks/queries/use-todo-data-by-id.ts

import { fetchTodoById } from "@/api/fetch-todo-by-id";
import { useQuery } from "@tanstack/react-query";

export function useTodoDataById(id: number) {
  return useQuery({
    queryFn: () => fetchTodoById(id),
    queryKey: ["todos", id],

    staleTime: 300000,  // 5분 (fresh → stale)
    gcTime: 5000,       // 5초 (inactive → deleted)

    // refetchOnMount: false,
    // refetchOnWindowFocus: false,
    // refetchOnReconnect: false,
    // refetchInterval: false,
  });
}
設定(せってい)意味(いみ)기본값(きほんち)
staleTimefresh 상태(じょうたい) 유지(ゆうじ) 시간(じかん)0ms
gcTimeinactive → deleted 시간(じかん)5분(ふん)

주의: 뒤로가기로 inactive 상태가 되면 staleTime은 의미 없음

完全(かんぜん)な状態遷移(じょうたいせんい)図(ず)

┌─────────┐
│ fetching│ ← 데이터 요청 중
└────┬────┘
     ↓
┌─────────┐
│  fresh  │ ← 신선한 데이터
└────┬────┘
     ↓ (staleTime 경과)
┌─────────┐
│  stale  │ ← 오래된 데이터
└────┬────┘
     ↓ (컴포넌트 언마운트)
┌─────────┐
│ inactive│ ← 사용되지 않음
└────┬────┘
     ↓ (gcTime 경과)
┌─────────┐
│ deleted │ ← 메모리에서 삭제
└─────────┘

🌐 グローバル設定(せってい)

QueryClient のデフォルト設定(せってい)

すべてのクエリに適用(てきよう)可能(かのう)な設定(せってい)です。
各(かく)クエリで個別設定(こべつせってい)しなくても自動適用(じどうてきよう)されます。

모든 쿼리에 적용 가능한 설정입니다.
각 쿼리에서 개별 설정하지 않아도 자동 적용됩니다.

main.tsx

import { createRoot } from "react-dom/client";
import "./index.css";
import App from "./App.tsx";
import { BrowserRouter } from "react-router";
import { QueryClientProvider, QueryClient } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 60000,           // 1분
      gcTime: 300000,             // 5분
      refetchOnMount: true,       // 마운트 시 리페칭
      refetchOnWindowFocus: false, // 윈도우 포커스 시 리페칭 안 함
      refetchOnReconnect: false,  // 재연결 시 리페칭 안 함
    },
  },
});

createRoot(document.getElementById("root")!).render(
  <BrowserRouter>
    <QueryClientProvider client={queryClient}>
      <ReactQueryDevtools />
      <App />
    </QueryClientProvider>
  </BrowserRouter>,
);
設定項目(せっていこうもく)説明(せつめい)推奨値(すいしょうち)
staleTimefresh 유지(ゆうじ) 시간(じかん)프로젝트에 따라
gcTime캐시 보관(ほかん) 시간(じかん)5분(ふん) (기본값(きほんち))
refetchOnMount마운트 시(じ) 리페칭true (추천(すいせん))
refetchOnWindowFocus포커스 시(じ) 리페칭false
refetchOnReconnect재연결(さいれんけつ) 시(じ) 리페칭false

권장 설정: refetchOnMount만 true, 나머지는 false로 설정하는 경우가 많음


🔄 データ変更要請(へんこうようせい) - useMutation

새로운 API 作成(さくせい)

データ追加(ついか)、修正(しゅうせい)、削除(さくじょ)には別途(べっと) APIが必要(ひつよう)です。
非同期関数(ひどうきかんすう)でPOST要請(ようせい)を送(おく)ります。

데이터 추가, 수정, 삭제에는 별도 API가 필요합니다.
비동기 함수로 POST 요청을 보냅니다.

src/api/create-todo.ts

import { API_URL } from "@/lib/constants";
import type { Todo } from "@/types";

export async function createTodo(content: string) {
  const response = await fetch(`${API_URL}/todos`, {
    method: "POST",
    body: JSON.stringify({
      content,          // 사용자 입력값
      isDone: false,
    }),
  });
  if (!response.ok) throw new Error("Create Todo Failed");

  const data: Todo = await response.json();
  return data;
}
HTTP メソッド用途(ようと)
POST데이터 추가(ついか)
PUT/PATCH데이터 수정(しゅうせい)
DELETE데이터 削除(さくじょ)

useMutation フック

バックエンドサーバーのデータを修正(しゅうせい)します。
useQueryではなくuseMutationを使用(しよう)します。

백엔드 서버의 데이터를 수정합니다.
useQuery가 아닌 useMutation을 사용합니다.

src/hooks/mutations/use-create-todo-mutation.ts

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

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

  return useMutation({
    mutationFn: createTodo,
    onMutate: () => {},     // 실행 전
    onSettled: () => {},    // 완료 후 (성공/실패 무관)
    onSuccess: () => {      // 성공 시
      window.location.reload();
    },
    onError: (error) => {   // 실패 시
      window.alert(error.message);
    },
  });
}
コールバック実行時点(じっこうじてん)
onMutatemutation 시작(しさく) 전(まえ)
onSuccess성공(せいこう) 시(じ)
onError실패(しっぱい) 시(じ)
onSettled완료(かんりょう) 후(ご) (성공(せいこう)/실패(しっぱい) 무관(むかん))

コンポーネントで使用(しよう)

components/todo-list/todo-editor.tsx

import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { useCreateTodoMutation } from "@/hooks/mutations/use-create-todo-mutation";
import { useState } from "react";

export default function TodoEditor() {
  const { mutate, isPending } = useCreateTodoMutation();
  const [content, setContent] = useState("");

  const handleAddClick = () => {
    if (content.trim() === "") return;
    mutate(content);
    setContent("");
  };

  return (
    <div className="flex gap-2">
      <Input
        value={content}
        onChange={(e) => setContent(e.target.value)}
        placeholder="새로운 할 일을 입력하세요 ..."
      />
      <Button disabled={isPending} onClick={handleAddClick}>
        추가
      </Button>
    </div>
  );
}
返却値(へんきゃくち)説明(せつめい)
mutatemutation 실행(じっこう) 함수(かんすう)
isPending로딩 상태(じょうたい)
isError에러 상태(じょうたい)
isSuccess성공(せいこう) 상태(じょうたい)

로딩 처리: isPending을 이용해 버튼 비활성화


🔄 キャッシュデータ操作(そうさ) 1 - 無効化(むこうか)

問題点(もんだいてん)

window.location.reload()はブラウザ全体(ぜんたい)を再読込(さいよみこみ)します。
すべてをリロードするため効率的(こうりつてき)ではありません。

window.location.reload()는 브라우저 전체를 새로고침합니다.
모든 것을 리로드하므로 비효율적입니다.

キャッシュ無効化(むこうか)

特定(とくてい)のキャッシュだけを無効化(むこうか)して再読込(さいよみこみ)します。
invalidateQueriesメソッドを使用(しよう)します。

특정 캐시만 무효화해서 다시 불러옵니다.
invalidateQueries 메서드를 사용합니다.

src/hooks/mutations/use-create-todo-mutation.ts

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

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

  return useMutation({
    mutationFn: createTodo,
    onMutate: () => {},
    onSettled: () => {},
    onSuccess: () => {
      queryClient.invalidateQueries({
        queryKey: QUERY_KEYS.todo.list,
      });
    },
    onError: (error) => {
      window.alert(error.message);
    },
  });
}

무효화: 해당 queryKey의 캐시를 stale 상태로 만들어 자동 리페칭

クエリキーファクトリー

queryKeyを重複(じゅうふく)しないように管理(かんり)します。
一元的(いちげんてき)に管理(かんり)して変更(へんこう)に強(つよ)くします。

queryKey를 중복되지 않도록 관리합니다.
일원적으로 관리해서 변경에 강하게 만듭니다.

lib/constants.ts

export const API_URL = "http://localhost:3000";

export const QUERY_KEYS = {
  todo: {
    all: ["todo"],
    list: ["todo", "list"],
    detail: (id: string) => ["todo", "detail", id],
  },
};

src/hooks/queries/use-todos-data.ts

import { fetchTodos } from "@/api/fetch-todos";
import { QUERY_KEYS } from "@/lib/constants";
import { useQuery } from "@tanstack/react-query";

export function useTodosData() {
  return useQuery({
    queryFn: fetchTodos,
    queryKey: QUERY_KEYS.todo.list,
  });
}
クエリキー用途(ようと)
QUERY_KEYS.todo.all모든 todo 관련(かんれん) 캐시
QUERY_KEYS.todo.listtodo 목록(もくろく)
QUERY_KEYS.todo.detail(id)특정(とくてい) todo 상세(しょうさい)

쿼리 키 팩토리: 불필요한 데이터 리페칭 방지, 유지보수성 향상


🎯 キャッシュデータ操作(そうさ) 2 - 直接更新(ちょくせつこうしん)

概念(がいねん)

リフェッチするデータが多(おお)い場合(ばあい)、応答結果値(おうとうけっかち)を利用(りよう)します。
サーバーに再要請(さいようせい)せず、キャッシュを直接更新(ちょくせつこうしん)します。

리페칭할 데이터가 많은 경우 응답 결과값을 이용합니다.
서버에 재요청하지 않고 캐시를 직접 업데이트합니다.

src/hooks/mutations/use-create-todo-mutation.ts

import { createTodo } from "@/api/create-todo";
import { QUERY_KEYS } from "@/lib/constants";
import type { Todo } from "@/types";
import { useMutation, useQueryClient } from "@tanstack/react-query";

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

  return useMutation({
    mutationFn: createTodo,
    onMutate: () => {},
    onSettled: () => {},
    onSuccess: (newTodo) => {
      queryClient.setQueryData<Todo[]>(
        QUERY_KEYS.todo.list,
        (prevTodos) => {
          if (!prevTodos) return [newTodo];
          return [...prevTodos, newTodo];
        }
      );
    },
    onError: (error) => {
      window.alert(error.message);
    },
  });
}

動作(どうさ)フロー

1. 사용자 입력 → mutate(content) 실행
2. 서버에 POST 요청
3. 서버 응답: newTodo 반환
4. onSuccess: 캐시에 newTodo 직접 추가
5. 화면 즉시 업데이트 (리페칭 없음)
方法(ほうほう)장점(ちょうてん)단점(たんてん)
invalidateQueries서버 데이터와 동기화(どうきか) 보장(ほしょう)네트워크 요청(ようせい) 발생(はっせい)
setQueryData즉시(そくじ) 반영(はんえい), 네트워크 요청(ようせい) 없음서버 데이터와 불일치(ふいっち) 가능(かのう)

최적 방법: setQueryData로 즉시 반영 + onSettled에서 invalidateQueries로 검증


📋 まとめ

TanStack QueryのGC Timeでメモリを効率的(こうりつてき)に管理(かんり)します。
useMutationでデータ変更(へんこう)を処理(しょり)し、キャッシュ無効化(むこうか)または直接更新(ちょくせつこうしん)で画面(がめん)を同期化(どうきか)します。

TanStack Query의 GC Time으로 메모리를 효율적으로 관리합니다.
useMutation으로 데이터 변경을 처리하고 캐시 무효화 또는 직접 업데이트로 화면을 동기화합니다.

핵심: staleTime (신선도) → gcTime (메모리 관리) → invalidateQueries (재검증) → setQueryData (즉시 반영)

profile
日本での就職を目指している26歳の韓国人開発者です。 アプリとweb開発、両方準備中で、日本語で技術概念を整理しながら日本語も一緒に勉強する予定です。 コツコツ続けるのが好きな開発者の成長記録を、一緒に見守っていただけると嬉しいです! 일본에서의 취업을 목표로 하고 있는 26살의 한국인 개발자입니다. 앱과 웹 개발, 둘 다 준비 중이며, 일본어로 기술 개념을 정리하면서 일본어도 함께 공부할 예정입니다. 꾸준히 계속하는 것을 좋아하는 개발자의 성장 기록을, 함께 지켜봐

0개의 댓글