freshまたはstaleからinactiveに変(か)わる状況(じょうきょう)です。
このキャッシュデータを活用(かつよう)するコンポーネントがないときに発生(はっせい)します。
fresh 또는 stale에서 inactive로 변하는 상황입니다.
이 캐시 데이터를 활용하는 컴포넌트가 없을 때 발생합니다.
状態 전환:
fresh/stale (사용 중)
↓
컴포넌트 언마운트
↓
inactive (사용되지 않음)
キャッシュデータが現在(げんざい)使用(しよう)されていない状態(じょうたい)です。
多(おお)すぎるとメモリ浪費(ろうひ)につながるため、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,
});
}
| 設定(せってい) | 意味(いみ) | 기본값(きほんち) |
|---|---|---|
staleTime | fresh 상태(じょうたい) 유지(ゆうじ) 시간(じかん) | 0ms |
gcTime | inactive → deleted 시간(じかん) | 5분(ふん) |
주의: 뒤로가기로 inactive 상태가 되면 staleTime은 의미 없음
┌─────────┐
│ fetching│ ← 데이터 요청 중
└────┬────┘
↓
┌─────────┐
│ fresh │ ← 신선한 데이터
└────┬────┘
↓ (staleTime 경과)
┌─────────┐
│ stale │ ← 오래된 데이터
└────┬────┘
↓ (컴포넌트 언마운트)
┌─────────┐
│ inactive│ ← 사용되지 않음
└────┬────┘
↓ (gcTime 경과)
┌─────────┐
│ deleted │ ← 메모리에서 삭제
└─────────┘
すべてのクエリに適用(てきよう)可能(かのう)な設定(せってい)です。
各(かく)クエリで個別設定(こべつせってい)しなくても自動適用(じどうてきよう)されます。
모든 쿼리에 적용 가능한 설정입니다.
각 쿼리에서 개별 설정하지 않아도 자동 적용됩니다.
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>,
);
| 設定項目(せっていこうもく) | 説明(せつめい) | 推奨値(すいしょうち) |
|---|---|---|
staleTime | fresh 유지(ゆうじ) 시간(じかん) | 프로젝트에 따라 |
gcTime | 캐시 보관(ほかん) 시간(じかん) | 5분(ふん) (기본값(きほんち)) |
refetchOnMount | 마운트 시(じ) 리페칭 | true (추천(すいせん)) |
refetchOnWindowFocus | 포커스 시(じ) 리페칭 | false |
refetchOnReconnect | 재연결(さいれんけつ) 시(じ) 리페칭 | false |
권장 설정: refetchOnMount만 true, 나머지는 false로 설정하는 경우가 많음
データ追加(ついか)、修正(しゅうせい)、削除(さくじょ)には別途(べっと) 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 | 데이터 削除(さくじょ) |
バックエンドサーバーのデータを修正(しゅうせい)します。
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);
},
});
}
| コールバック | 実行時点(じっこうじてん) |
|---|---|
onMutate | mutation 시작(しさく) 전(まえ) |
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>
);
}
| 返却値(へんきゃくち) | 説明(せつめい) |
|---|---|
mutate | mutation 실행(じっこう) 함수(かんすう) |
isPending | 로딩 상태(じょうたい) |
isError | 에러 상태(じょうたい) |
isSuccess | 성공(せいこう) 상태(じょうたい) |
로딩 처리: isPending을 이용해 버튼 비활성화
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.list | todo 목록(もくろく) |
QUERY_KEYS.todo.detail(id) | 특정(とくてい) todo 상세(しょうさい) |
쿼리 키 팩토리: 불필요한 데이터 리페칭 방지, 유지보수성 향상
リフェッチするデータが多(おお)い場合(ばあい)、応答結果値(おうとうけっかち)を利用(りよう)します。
サーバーに再要請(さいようせい)せず、キャッシュを直接更新(ちょくせつこうしん)します。
리페칭할 데이터가 많은 경우 응답 결과값을 이용합니다.
서버에 재요청하지 않고 캐시를 직접 업데이트합니다.
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 (즉시 반영)