--- id: react-state-library-comparison title: 상태 라이브러리 비교 — Zustand / Jotai / Redux Toolkit category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [react, state, zustand, jotai, redux, vibe-coding] tech_stack: { language: "TypeScript / React", applicable_to: ["Frontend"] } applied_in: [] aliases: [Zustand, Jotai, Redux Toolkit, Valtio, state management, store] --- # 상태 라이브러리 비교 > Server state = TanStack Query. **Client-only UI state** = Zustand 디폴트, 원자 단위 derive 많으면 Jotai, 큰 팀 + DevTools = Redux Toolkit. Context = 가벼운 prop drilling 만. ## 📖 핵심 개념 - Zustand: 단일 store, hook 기반, 가장 간단. - Jotai: 원자 단위, derive (computed) 강력. - Redux Toolkit: 전통, slice/reducer/devtools. - Valtio: proxy mutate 스타일. - Context: React 내장, re-render 비용 큼. ## 💻 코드 패턴 ### Zustand ```ts import { create } from 'zustand'; interface CartStore { items: Item[]; add: (i: Item) => void; remove: (id: string) => void; total: () => number; } export const useCart = create((set, get) => ({ items: [], add: (i) => set(s => ({ items: [...s.items, i] })), remove: (id) => set(s => ({ items: s.items.filter(x => x.id !== id) })), total: () => get().items.reduce((s, i) => s + i.price, 0), })); // 사용 const items = useCart(s => s.items); // selective subscribe const add = useCart(s => s.add); ``` ### Zustand + persist ```ts import { persist, createJSONStorage } from 'zustand/middleware'; const useAuth = create(persist( (set) => ({ token: null, login: (t) => set({ token: t }) }), { name: 'auth', storage: createJSONStorage(() => localStorage) } )); ``` ### Jotai ```ts import { atom, useAtom, useAtomValue } from 'jotai'; const cartAtom = atom([]); const totalAtom = atom((get) => get(cartAtom).reduce((s, i) => s + i.price, 0)); function Cart() { const [items, setItems] = useAtom(cartAtom); const total = useAtomValue(totalAtom); // 자동 derive return

{items.length} items, {total}

; } ``` ### Redux Toolkit ```ts import { createSlice, configureStore } from '@reduxjs/toolkit'; const cart = createSlice({ name: 'cart', initialState: { items: [] as Item[] }, reducers: { add: (s, a: PayloadAction) => { s.items.push(a.payload); }, remove: (s, a: PayloadAction) => { s.items = s.items.filter(x => x.id !== a.payload); }, }, }); export const store = configureStore({ reducer: { cart: cart.reducer } }); export type RootState = ReturnType; export type AppDispatch = typeof store.dispatch; ``` ```tsx // hooks.ts import { useDispatch, useSelector } from 'react-redux'; export const useAppDispatch = useDispatch.withTypes(); export const useAppSelector = useSelector.withTypes(); // component const items = useAppSelector(s => s.cart.items); const dispatch = useAppDispatch(); dispatch(cart.actions.add(item)); ``` ### Valtio (mutate 스타일) ```ts import { proxy, useSnapshot } from 'valtio'; const state = proxy({ count: 0 }); state.count++; // OK function Counter() { const snap = useSnapshot(state); return

{snap.count}

; } ``` ### Context (가벼운 케이스) ```tsx const Theme = createContext<'light' | 'dark'>('light'); function App() { const [theme, setTheme] = useState<'light' | 'dark'>('light'); return ; } ``` ⚠️ value 가 자주 바뀌면 모든 consumer 재렌더 → Zustand/Jotai 가 나음. ## 🤔 의사결정 기준 | 상황 | 추천 | |---|---| | Server data | TanStack Query | | 작은 UI state (toggle, modal) | useState | | 글로벌 client state | Zustand | | Computed state 많음 | Jotai | | 큰 팀 / DevTools / time-travel | Redux Toolkit | | Mutate 친화 | Valtio | | Provider tree 만 | Context | | RSC + Next 14 | useState + URL state + Query | ## ❌ 안티패턴 - **모든 state 글로벌**: 컴포넌트 캡슐화 깨짐. 가능한 local. - **Server data 를 Zustand 에 복사**: 동기화 지옥. Query 만. - **Context 자주 변경**: 모든 자식 재렌더. - **Redux 한 슬라이스 안 큰 객체 매 변경**: 모든 selector 재계산. - **Persist 모든 store**: 비밀 / 임시 까지 저장. - **Selector 안에 객체 새로 만듦**: 매번 새 reference — re-render. shallow 또는 reselect. - **Selectors 간 derive 깊음**: Jotai / reselect. ## 🤖 LLM 활용 힌트 - Server = Query, Client = Zustand 디폴트. - Computed 필요 시 Jotai. - Devtools 강력 = Redux Toolkit. ## 🔗 관련 문서 - [[React_TanStack_Query_Advanced]] - [[React_Context_API_Misuse]]