--- id: wiki-2026-0508-상태-관리-최적화-zustand-jotai-valtio title: 상태 관리 최적화 (Zustand Jotai Valtio) category: 10_Wiki/Topics status: verified canonical_id: self aliases: [Modern React State, Zustand Jotai Valtio, Atomic State Management] duplicate_of: none source_trust_level: A confidence_score: 0.9 verification_status: applied tags: [react, state-management, zustand, jotai, valtio, performance] raw_sources: [] last_reinforced: 2026-05-10 github_commit: pending tech_stack: language: typescript framework: React-19 --- # 상태 관리 최적화 (Zustand · Jotai · Valtio) ## 매 한 줄 > **"매 right tool 의 매 right granularity"**. 매 React 19 (2026) 시대, Redux 의 boilerplate 없이 매 selective subscription 으로 re-render 최소화 — Zustand (store-based, 매 simple), Jotai (atomic, 매 fine-grained), Valtio (proxy-based, 매 mutable feel) 의 매 성격 다른 trio. ## 매 핵심 ### 매 세 라이브러리 비교 - **Zustand**: 단일 store + selector subscription. 매 Redux replacement 의 80% 케이스. ~3kb. - **Jotai**: atom 단위 state, atom 의 dependency graph. 매 fine-grained re-render. ~5kb. - **Valtio**: proxy 로 mutable mutation, 매 reactive subscribe. ~3kb. 매 immer 대체. - 매 셋 모두 React 19 의 useSyncExternalStore 와 매 native concurrent rendering 지원. ### 매 선택 축 - **Granularity**: Jotai (atom) > Valtio (proxy path) > Zustand (selector). - **Mental model**: Zustand (store) > Valtio (mutable) > Jotai (atom graph). - **DevTools**: Zustand (Redux DevTools) > Jotai (Jotai DevTools) > Valtio (proxy log). ### 매 응용 1. **App-wide state (auth, theme)**: Zustand single store. 2. **Cross-component derived state**: Jotai atoms with `atom((get) => ...)`. 3. **Form / canvas / heavy mutation**: Valtio (mutable feel). ## 💻 패턴 ### Zustand store + selector ```typescript import { create } from 'zustand'; import { devtools, persist } from 'zustand/middleware'; interface AuthStore { user: User | null; login: (u: User) => void; logout: () => void; } export const useAuth = create()( devtools( persist( (set) => ({ user: null, login: (user) => set({ user }, false, 'auth/login'), logout: () => set({ user: null }, false, 'auth/logout'), }), { name: 'auth' }, ), ), ); // Component — selective subscribe const userName = useAuth((s) => s.user?.name); ``` ### Zustand slice pattern (large store) ```typescript const createAuthSlice: StateCreator = (set) => ({ /* ... */ }); const createCartSlice: StateCreator = (set) => ({ /* ... */ }); export const useStore = create()((...a) => ({ ...createAuthSlice(...a), ...createCartSlice(...a), })); ``` ### Jotai atomic state ```typescript import { atom, useAtom, useAtomValue } from 'jotai'; const countAtom = atom(0); const doubledAtom = atom((get) => get(countAtom) * 2); const asyncUserAtom = atom(async (get) => { const id = get(userIdAtom); return fetch(`/api/u/${id}`).then(r => r.json()); }); function Counter() { const [count, setCount] = useAtom(countAtom); const doubled = useAtomValue(doubledAtom); // re-renders only when doubled changes return ; } ``` ### Jotai atomFamily (dynamic per-id atoms) ```typescript import { atomFamily } from 'jotai/utils'; const todoAtomFamily = atomFamily((id: string) => atom({ id, text: '', done: false }), ); function Todo({ id }: { id: string }) { const [todo, setTodo] = useAtom(todoAtomFamily(id)); // only this Todo re-renders when its atom changes } ``` ### Valtio proxy mutable state ```typescript import { proxy, useSnapshot, subscribe } from 'valtio'; const canvasState = proxy({ shapes: [] as Shape[], selected: null as string | null, zoom: 1, }); // Mutation (no setState!) function addShape(s: Shape) { canvasState.shapes.push(s); } function ShapeList() { const snap = useSnapshot(canvasState); // immutable snapshot, fine-grained return <>{snap.shapes.map(s => )}; } ``` ### Re-render audit (React DevTools profiler) ```typescript // Zustand: shallow equal for object selectors import { shallow } from 'zustand/shallow'; const { x, y } = useStore((s) => ({ x: s.x, y: s.y }), shallow); // Jotai: selectAtom for partial reads import { selectAtom } from 'jotai/utils'; const userNameAtom = selectAtom(userAtom, (u) => u.name); ``` ### SSR / RSC integration (Next.js 15+) ```typescript // Zustand — per-request store (avoid module singleton on server) export const StoreProvider = ({ children, initialState }) => { const storeRef = useRef(); if (!storeRef.current) storeRef.current = createStore(initialState); return {children}; }; ``` ## 매 결정 기준 | 상황 | Approach | |---|---| | Simple global state (auth, UI) | **Zustand** | | Complex derived/async chains | **Jotai** | | Heavy mutation (canvas, editor, form) | **Valtio** | | Server state (queries, mutations) | **TanStack Query** (not these) | | Tree-wide config that rarely changes | React Context | | Time-travel + middleware ecosystem | Redux Toolkit (still valid) | **기본값**: 매 신규 React 19 앱 → Zustand. 매 fine-grained 필요 → Jotai 추가. 매 server state → TanStack Query separate. ## 🔗 Graph - 부모: [[Frontend-Performance]] - 변형: [[Redux-Toolkit]] · [[MobX]] · [[Recoil]] (legacy) - 응용: [[Optimistic-UI]] - Adjacent: [[React-Compiler]] ## 🤖 LLM 활용 **언제**: store shape 의 first scaffold, selector boilerplate 생성, 매 atom dependency graph 시각화. **언제 X**: 매 actual perf 측정 — profiler 만 truth. Library choice 의 매 nuanced trade-off 도 매 LLM 의 over-confident 영역. ## ❌ 안티패턴 - **Single mega-store** (Zustand): selector 로 mitigate 가능하지만, atom split 가 더 clean. - **Atom explosion** (Jotai): 매 trivial state 의 atom 화 → 매 cognitive overhead. - **Direct Valtio mutation in render**: 매 React rule violation. mutation 은 event handler 또는 effect 내. - **Context for high-frequency state**: 매 entire subtree re-render. 매 use these libs instead. - **Mixing server + client state**: 매 TanStack Query 의 영역, store 에 넣지 마. ## 🧪 검증 / 중복 - Verified (Zustand v5 docs 2026, Jotai v2 docs, Valtio v2 docs, Daishi Kato writings). - 신뢰도 A. ## 🕓 Changelog | 날짜 | 변경 | |---|---| | 2026-05-08 | Phase 1 | | 2026-05-10 | Manual cleanup — Zustand/Jotai/Valtio 비교 + React 19 패턴 |