Project-hongsta(5)

강홍규(カンホンギュ)·2026년 1월 24일

Project

목록 보기
5/9
post-thumbnail

TanStack Query - サーバー状態管理(じょうたいかんり)実装(じっそう)

🔧 実習用(じっしゅうよう)バックエンドサーバー設定(せってい)

JSON Server インストール

開発用(かいはつよう)のバックエンドサーバーを構築(こうちく)します。
JSON Serverを使(つか)って簡単(かんたん)にREST APIを作成(さくせい)できます。

개발용 백엔드 서버를 구축합니다.
JSON Server를 사용해서 간단하게 REST API를 만들 수 있습니다.

npm i json-server -D

データベース設定(せってい)

server/db.json

{
  "todos": [
    {
      "id": 1,
      "content": "Todo 1",
      "isDone": true
    },
    {
      "id": 2,
      "content": "Todo 2",
      "isDone": true
    },
    {
      "id": 3,
      "content": "Todo 3",
      "isDone": false
    }
  ]
}

Vite 設定(せってい)

serverフォルダー内(ない)のファイル変更(へんこう)を監視(かんし)しないようにします。
サーバーファイルが変(か)わってもリレンダリングが発生(はっせい)しません。

server 폴더 내의 파일 변경을 감시하지 않도록 합니다.
서버 파일이 변해도 리렌더링이 발생하지 않습니다.

vite.config.ts

export default defineConfig({
  // ...
  server: {
    watch: {
      ignored: ["**/server/**"],
    },
  },
});

サーバー起動(きどう)

ターミナルを2つ開(ひら)いて使用(しよう)します。
1つ目(め)はフロントエンド、2つ目(め)はバックエンドサーバーです。

터미널을 2개 열어서 사용합니다.
첫 번째는 프론트엔드, 두 번째는 백엔드 서버입니다.

# 터미널 1: 프론트엔드
npm run dev

# 터미널 2: 백엔드
npx json-server src/server/db.json
アクセス:
http://localhost:3000/todos     → 전체 todos
http://localhost:3000/todos/1   → id가 1인 todo

JSON Server: RESTful API를 자동으로 생성해주는 개발 도구


📦 TanStack Query 設定(せってい)

インストール

npm i @tanstack/react-query

QueryClient 設定(せってい)

QueryClientを一種(いっしゅ)の貯蔵庫(ちょぞうこ)として考(かんが)えます。
キャッシング値(あたい)などを管理(かんり)します。

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";

const queryClient = new QueryClient();

createRoot(document.getElementById("root")!).render(
  <BrowserRouter>
    <QueryClientProvider client={queryClient}>
      <App />
    </QueryClientProvider>
  </BrowserRouter>,
);
要素(ようそ)役割(やくわり)
QueryClient캐시 저장소(ちょぞうしょ)
QueryClientProviderReact 앱에 QueryClient 제공(ていきょう)

🔌 バックエンドデータ活用(かつよう)

API 定数(ていすう)設定(せってい)

API URLを毎回(まいかい)設定(せってい)しないようにします。
定数(ていすう)ファイルで一元管理(いちげんかんり)します。

API URL을 매번 설정하지 않도록 합니다.
상수 파일로 일원 관리합니다.

lib/constants.ts

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

Fetch 関数作成(かんすうさくせい)

src/api/fetch-todos.ts

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

export async function fetchTodos() {
  const response = await fetch(`${API_URL}/todos`);
  if (!response.ok) throw new Error("Fetch Failed");

  const data: Todo[] = await response.json();
  return data;
}

カスタムフック作成(さくせい)

TanStack Queryを使(つか)ってデータを照会(しょうかい)するフックを作(つく)ります。
queryFnとqueryKeyを設定(せってい)します。

TanStack Query를 사용해서 데이터를 조회하는 훅을 만듭니다.
queryFn과 queryKey를 설정합니다.

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

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

export function useTodosData() {
  return useQuery({
    queryFn: fetchTodos,
    queryKey: ["todos"],
  });
}
プロパティ説明(せつめい)
queryFn데이터를 가져오는 비동기(ひどうき) 함수(かんすう)
queryKey캐시 식별(しきべつ)을 위한 고유(こゆう) 키(き)

ページで使用(しよう)

pages/todo-list-page.tsx

import TodoEditor from "@/components/todo-list/todo-editor";
import TodoItem from "@/components/todo-list/todo-item";
import { useTodosData } from "@/hooks/queries/use-todos-data";

export default function TodoListPage() {
  const { data: todos, isLoading, error } = useTodosData();

  if (error) return <div>오류가 발생했습니다.</div>;
  if (isLoading) return <div>로딩 중 입니다 ...</div>;

  return (
    <div className="flex flex-col gap-5 p-5">
      <h1 className="text-2xl font-bold">TodoList</h1>
      <TodoEditor />
      {todos?.map((todo) => <TodoItem key={todo.id} {...todo} />)}
    </div>
  );
}
返却値(へんきゃくち)説明(せつめい)
data가져온 데이터
isLoading로딩 상태(じょうたい)
error에러 정보(じょうほう)

💾 キャッシングメカニズム理解(りかい)

キャッシング概念(がいねん)

要請(ようせい)を通(とお)して読(よ)み込(こ)んだデータをqueryKeyを利用(りよう)してキャッシングします。
特定時間(とくていじかん)の間(あいだ)、不要(ふよう)な要請(ようせい)を防止(ぼうし)します。

요청을 통해 불러온 데이터를 queryKey를 이용해서 캐싱합니다.
특정 시간 동안 불필요한 요청을 방지합니다.

キャッシング フロー:

1. 요청(ようせい) → 데이터 가져오기
2. queryKey로 캐시에 저장(ちょぞう)
3. 동일(どういつ)한 queryKey 요청(ようせい) → 캐시 데이터 사용(しよう)
4. 적절(てきせつ)한 타이밍에 자동(じどう) 갱신(こうしん)

5つの状態(じょうたい)

データは5つの状態(じょうたい)を持(も)ちます。
fetching、fresh、staleが主要(しゅよう)な状態(じょうたい)です。

데이터는 5개의 상태를 가집니다.
fetching, fresh, stale이 주요 상태입니다.

状態 遷移(せんい):

fetching (データ読込中(よみこみちゅう))
    ↓
fresh (新鮮(しんせん)な状態(じょうたい))
    ↓ (staleTime 経過(けいか))
stale (古(ふる)い状態(じょうたい))
    ↓ (리페칭 트리거)
fetching (再読込(さいよみこみ))
状態(じょうたい)説明(せつめい)
fetching데이터를 불러오는 중(ちゅう)
fresh데이터가 신선(しんせん)한 상태(じょうたい)
stale데이터가 오래된 상태(じょうたい)
inactive사용(しよう)되지 않는 캐시
deleted삭제(さくじょ)된 캐시

staleTime

新鮮(しんせん)な状態(じょうたい)を維持(いじ)する時間(じかん)です。
賞味期限(しょうみきげん)のようなものです。

신선한 상태를 유지하는 시간입니다.
유통 기한 같은 것입니다.

useQuery({
  queryFn: fetchTodos,
  queryKey: ["todos"],
  staleTime: 5000, // 5초 동안 fresh 상태 유지
});

リフェッチングトリガー

4つのタイミングでデータを再読込(さいよみこみ)します。
stale状態(じょうたい)からfetching状態(じょうたい)に変(か)わります。

4가지 타이밍에 데이터를 다시 불러옵니다.
stale 상태에서 fetching 상태로 변합니다.

トリガー説明(せつめい)
mount컴포넌트가 마운트될 때
windowFocus사용자(しようしゃ)가 탭으로 돌아올 때
reconnect인터넷이 다시 연결(れんけつ)될 때
interval특정(とくてい) 시간(じかん) 주기(しゅうき)로
useQuery({
  queryFn: fetchTodos,
  queryKey: ["todos"],
  
  // 리페칭 설정
  refetchOnMount: true,        // 마운트 시
  refetchOnWindowFocus: true,  // 윈도우 포커스 시
  refetchOnReconnect: true,    // 재연결 시
  refetchInterval: 10000,      // 10초마다
});

🔍 React Query Devtools

インストールと設定(せってい)

視覚的(しかくてき)にキャッシュ状態(じょうたい)を確認(かくにん)できます。
開発中(かいはつちゅう)にデータの流(なが)れを把握(はあく)するのに便利(べんり)です。

시각적으로 캐시 상태를 확인할 수 있습니다.
개발 중에 데이터 흐름을 파악하는 데 편리합니다.

npm i @tanstack/react-query-devtools

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();

createRoot(document.getElementById("root")!).render(
  <BrowserRouter>
    <QueryClientProvider client={queryClient}>
      <ReactQueryDevtools />
      <App />
    </QueryClientProvider>
  </BrowserRouter>,
);

Devtools: 화면 하단에 아이콘 표시 → 클릭하면 캐시 상태 확인 가능


📄 Todo 詳細(しょうさい)ページ実装(じっそう)

ルーティング追加(ついか)

App.tsx

import { Outlet, Route, Routes } from "react-router";
import "./App.css";
import IndexPage from "@/pages/index-page";
import CounterPage from "./pages/counter-page";
import TodoListPage from "@/pages/todo-list-page";
import TodoDetailPage from "@/pages/todo-detail-page";

function App() {
  return (
    <Routes>
      <Route path="/" element={<IndexPage />} />
      <Route path="/counter" element={<CounterPage />} />
      <Route path="/todolist" element={<TodoListPage />} />
      <Route path="/todolist/:id" element={<TodoDetailPage />} />
      
      {/* ... */}
    </Routes>
  );
}

export default App;

ID別(べつ) Fetch 関数(かんすう)

src/api/fetch-todo-by-id.ts

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

export async function fetchTodoById(id: number) {
  const response = await fetch(`${API_URL}/todos/${id}`);
  if (!response.ok) throw new Error("Fetch Failed");

  const data: Todo = await response.json();
  return data;
}

ID別(べつ) カスタムフック

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: 5000,

    // 선택적 리페칭 설정
    // refetchOnMount: false,
    // refetchOnWindowFocus: false,
    // refetchOnReconnect: false,
    // refetchInterval: false,
  });
}

queryKey: ["todos", id]로 개별 todo에 대한 캐시 생성

詳細(しょうさい)ページコンポーネント

pages/todo-detail-page.tsx

import { useTodoDataById } from "@/hooks/queries/use-todo-data-by-id";
import { useParams } from "react-router";

export default function TodoDetailPage() {
  const params = useParams();
  const id = params.id;

  const { data, isLoading, error } = useTodoDataById(Number(id));

  if (isLoading) return <div>로딩 중 입니다 ...</div>;
  if (error || !data) return <div>오류가 발생했습니다</div>;

  return <div>{data.content}</div>;
}

TodoItem にリンク追加(ついか)

components/todo-list/todo-item.tsx

import { Button } from "@/components/ui/button";
import { useDeleteTodo } from "@/store/todos";
import { Link } from "react-router";

export default function TodoItem({
  id,
  content,
}: {
  id: number;
  content: string;
}) {
  const deleteTodo = useDeleteTodo();

  const handleDeleteClick = () => {
    deleteTodo(id);
  };

  return (
    <div className="flex items-center justify-between border p-2">
      <Link to={`/todolist/${id}`}>{content}</Link>
      <Button onClick={handleDeleteClick} variant={"destructive"}>
        삭제
      </Button>
    </div>
  );
}

📋 まとめ

TanStack Queryで強力(きょうりょく)なキャッシング機能(きのう)を活用(かつよう)できます。
ローディング画面(がめん)を最小限(さいしょうげん)にし、自動(じどう)最適化(さいてきか)でアプリ性能(せいのう)が向上(こうじょう)します。

TanStack Query로 강력한 캐싱 기능을 활용할 수 있습니다.
로딩 화면을 최소화하고 자동 최적화로 앱 성능이 향상됩니다.

핵심: JSON Server (백엔드) + TanStack Query (캐싱) + Devtools (디버깅) = 최적화된 서버 상태 관리

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

0개의 댓글