Files
2nd/10_Wiki/Topics/Domain_Programming/AI_and_ML/상태 관리 및 API 응답 모델링(State Management and API Response Modeling).md
T
Antigravity Agent c24165b8bc refactor(topics): 멀티 에이전트용 지식 재편 — _Common(공통 기본기) + Domain_* 구조
에이전트 8종(대화형/프로그래머 C·S/디자이너/설계자/기획자/QA/PD/PM)에게
[공통 기본 능력 + 롤별 Specialty] 2층으로 지식을 주입하기 위한 재분류.
문서 내용·포맷은 무수정, 폴더 이동만 (6,372개 문서 수 보존 확인).

- Topic_Programming → Domain_Programming (내부 구조 보존)
- Topic_Graphic → Domain_Design
- Topic_Business → Domain_Product
- Topic_General → Domain_General
- _Common 신설: Math(구 Topic_Math_Specialty), Reasoning(구 General/From_Thinking & Reasoning),
  Reasoning_Creativity(구 General/From_창의성), Communication(Poetic_Blog_Writing + From_writing)
- 타 도메인의 From_* 폴더는 유지 (출처 표기일 뿐, 이미 도메인에 맞게 분류된 문서)
- 빈 폴더 정리 (memory/procedures)
- 에이전트→폴더 매핑은 workspace의 .astra/agent-knowledge-map.json (9개 에이전트)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 11:05:56 +09:00

7.9 KiB

id, title, category, status, canonical_id, aliases, duplicate_of, source_trust_level, confidence_score, verification_status, tags, raw_sources, last_reinforced, github_commit, tech_stack
id title category status canonical_id aliases duplicate_of source_trust_level confidence_score verification_status tags raw_sources last_reinforced github_commit tech_stack
wiki-2026-0508-상태-관리-및-api-응답-모델링-state-managem 상태 관리 및 API 응답 모델링 (State Management and API Response Modeling) 10_Wiki/Topics verified self
State Management
API Response Modeling
Server State
Client State
none A 0.9 applied
state-management
api
react
tanstack-query
zustand
frontend
2026-05-10 pending
language framework
typescript React 19/TanStack Query v5/Zustand

상태 관리 및 API 응답 모델링

매 한 줄

"매 server state 와 client state 의 분리 가 modern 의 시작". 매 2026 standard React app 에서 server cache (TanStack Query / SWR / RTK Query) + client UI state (Zustand / Jotai / useState) 의 명확한 boundary 가 default — 매 redux-thunk 의 monolithic global store 시대 는 끝났고, 매 API response 는 typed schema (Zod/Valibot) 로 runtime validation 의 거침.

매 핵심

매 두 종류 의 state

  • Server state: server 가 source of truth. 매 cache, refetch, invalidate, optimistic update. → TanStack Query / SWR / RTK Query / Apollo.
  • Client state: UI 만 의 관심. modal open/close, theme, form draft. → useState / Zustand / Jotai / Valtio.
  • URL state: 매 search/filter 의 URL 동기화 (nuqs, TanStack Router).
  • Form state: React Hook Form / TanStack Form.

매 API response 의 modeling

  1. OpenAPI spec → codegen (orval, openapi-typescript): 매 타입 + client 의 자동 생성.
  2. tRPC: 매 server-client end-to-end type. 매 BFF pattern 에 적합.
  3. GraphQL + codegen: 매 graphql-codegen + Apollo / Relay.
  4. Manual + Zod: 매 small project 에 OK. 매 runtime validation 가능.

매 normalization 의 trade-off

  • 매 normalized (entities by id) — 매 RTK Query / Apollo. 매 mutation 시 efficient cache 갱신.
  • 매 denormalized (response shape 그대로) — 매 TanStack Query default. 매 simpler.
  • 매 2026 trend: 매 simpler default + 매 specific case 만 normalize.

매 응용

  1. Dashboard with multi-resource (TanStack Query + Zustand).
  2. Real-time chat (Query + WebSocket subscription).
  3. Optimistic UI (Query mutate with rollback).
  4. Offline-first (Query persistor + IndexedDB).

💻 패턴

Pattern 1 — Zod schema + TanStack Query

import { z } from "zod";
import { useQuery } from "@tanstack/react-query";

const User = z.object({
  id: z.string().uuid(),
  email: z.string().email(),
  name: z.string(),
  createdAt: z.coerce.date(),
});
type User = z.infer<typeof User>;

async function fetchUser(id: string): Promise<User> {
  const res = await fetch(`/api/users/${id}`);
  if (!res.ok) throw new Error("HTTP " + res.status);
  return User.parse(await res.json());   // runtime check
}

export function useUser(id: string) {
  return useQuery({ queryKey: ["user", id], queryFn: () => fetchUser(id),
                    staleTime: 60_000 });
}

Pattern 2 — Optimistic mutation

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

export function useToggleLike(postId: string) {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: () => fetch(`/api/posts/${postId}/like`, { method: "POST" }),
    onMutate: async () => {
      await qc.cancelQueries({ queryKey: ["post", postId] });
      const prev = qc.getQueryData<Post>(["post", postId]);
      qc.setQueryData<Post>(["post", postId], old =>
        old ? { ...old, liked: !old.liked, likes: old.likes + (old.liked ? -1 : 1) } : old);
      return { prev };
    },
    onError: (_e, _v, ctx) => ctx?.prev && qc.setQueryData(["post", postId], ctx.prev),
    onSettled: () => qc.invalidateQueries({ queryKey: ["post", postId] }),
  });
}

Pattern 3 — Zustand client store

import { create } from "zustand";
import { persist } from "zustand/middleware";

type UI = {
  theme: "light" | "dark";
  sidebar: boolean;
  setTheme: (t: UI["theme"]) => void;
  toggleSidebar: () => void;
};

export const useUI = create<UI>()(
  persist(
    set => ({
      theme: "light", sidebar: true,
      setTheme: t => set({ theme: t }),
      toggleSidebar: () => set(s => ({ sidebar: !s.sidebar })),
    }),
    { name: "ui-store" }
  )
);

Pattern 4 — Server / client boundary

// hooks/useDashboard.ts
export function useDashboard() {
  const userQ  = useUser("me");                    // server
  const ordersQ = useOrders({ status: "open" });   // server
  const sidebar = useUI(s => s.sidebar);           // client
  return {
    user: userQ.data, orders: ordersQ.data,
    isLoading: userQ.isLoading || ordersQ.isLoading,
    sidebar,
  };
}

Pattern 5 — tRPC end-to-end types

// server: routers/post.ts
import { z } from "zod"; import { publicProcedure, router } from "../trpc";
export const postRouter = router({
  list: publicProcedure.input(z.object({ limit: z.number().max(100) }))
    .query(({ input }) => db.post.findMany({ take: input.limit })),
  create: publicProcedure.input(z.object({ title: z.string().min(1) }))
    .mutation(({ input }) => db.post.create({ data: input })),
});

// client
const { data } = trpc.post.list.useQuery({ limit: 20 });   // fully typed

Pattern 6 — URL state (nuqs)

import { useQueryState, parseAsString, parseAsInteger } from "nuqs";

function ProductFilters() {
  const [q, setQ] = useQueryState("q", parseAsString.withDefault(""));
  const [page, setPage] = useQueryState("page", parseAsInteger.withDefault(1));
  return (
    <>
      <input value={q} onChange={e => setQ(e.target.value)} />
      <button onClick={() => setPage(page + 1)}>Next</button>
    </>
  );
}

Pattern 7 — Suspense + Error Boundary (React 19)

import { ErrorBoundary } from "react-error-boundary";
import { Suspense } from "react";

<ErrorBoundary fallbackRender={({ error }) => <p>Error: {error.message}</p>}>
  <Suspense fallback={<Skeleton />}>
    <Dashboard />
  </Suspense>
</ErrorBoundary>

Pattern 8 — Discriminated union response

type ApiResult<T> =
  | { status: "ok";    data: T }
  | { status: "error"; code: string; message: string }
  | { status: "loading" };

function render(r: ApiResult<User>) {
  switch (r.status) {
    case "loading": return <Skeleton />;
    case "error":   return <Err msg={r.message} />;
    case "ok":      return <UserCard u={r.data} />;
  }
}

매 결정 기준

상황 Tool
Server data fetch + cache TanStack Query (default)
Full-stack TS, BFF tRPC
Heavy entity normalization RTK Query / Apollo
Small client UI flag useState / Zustand
Atomic, fine-grained reactivity Jotai / Valtio
Form React Hook Form
URL filter state nuqs / TanStack Router
Runtime validation Zod / Valibot

기본값: 매 TanStack Query (server) + Zustand (client) + Zod (validation).

🔗 Graph

🤖 LLM 활용

언제: 매 OpenAPI spec → typed client codegen, 매 Zod schema 의 generation, 매 mutation 의 optimistic update boilerplate. 언제 X: 매 cache invalidation 의 business decision — 매 domain knowledge 가 필요.

안티패턴

  • useEffect + fetch: 매 race condition, no cache, no dedup. 매 TanStack Query 가 default.
  • Global redux for everything: 매 server state 의 cache 의 reinvent.
  • No runtime validation: 매 backend 변경 시 silent type-lie.
  • Mutation 후 manual refetch all: 매 invalidation pattern 의 무시.

🧪 검증 / 중복

  • Verified (TanStack Query v5 docs, tRPC v11, React 19 RFC).
  • 신뢰도 A.

🕓 Changelog

날짜 변경
2026-05-08 Phase 1
2026-05-10 Manual cleanup — server/client state 분리 + 8 patterns