TechFeedTechFeed
Frontend

React 상태 관리 비교 2026 — Zustand vs Jotai vs Redux Toolkit vs Valtio

React 상태 관리 라이브러리 4종의 번들 크기, API 설계 철학, 보일러플레이트, SSR 지원을 비교한다. Zustand의 단일 스토어 패턴, Jotai의 아토믹 조합, Redux Toolkit이 여전히 유효한 경우, 프로젝트 규모별 선택 기준과 서버/클라이언트 상태 분리 전략을 코드 예시와 함께 정리한다.

React 상태 관리 라이브러리는 Redux 독점 시대를 지나 Zustand, Jotai, Valtio, Recoil 등 경량 라이브러리가 주류가 됐다. 2026년 기준 npm 다운로드 수, 번들 크기, API 설계 철학, 실제 사용 패턴을 비교하고, 프로젝트 규모와 팀 상황별 선택 기준을 제시한다. Redux Toolkit이 여전히 유효한 경우와, 더 이상 Redux가 필요 없는 경우를 구분한다.

상태 관리가 복잡해지는 시점 — 언제 라이브러리가 필요한가

React의 기본 상태 관리(useState, useContext)로 충분한 경우가 많다. 상태 관리 라이브러리를 도입해야 하는 명확한 시그널은 다음과 같다:

  • Prop Drilling 3단계 이상: 부모 → 자식 → 손자 → 증손자로 props가 관통하면서 중간 컴포넌트가 불필요한 props를 전달만 하고 있다
  • Context 성능 문제: useContext를 쓰는 컴포넌트가 20개 이상이면, 하나의 상태 변경이 모든 구독 컴포넌트를 리렌더링한다
  • 서버 상태와 클라이언트 상태 분리 필요: API 응답 캐싱과 UI 상태(모달 열림, 탭 선택 등)를 별도 레이어로 관리해야 할 때
  • 복잡한 상태 전이: 여러 상태가 서로 의존하고, 특정 순서로 변경되어야 하는 비즈니스 로직
2026년 핵심 흐름: 서버 상태(API 데이터)는 TanStack Query(React Query)로, 클라이언트 상태(UI 상태)는 Zustand 또는 Jotai로 관리하는 조합이 사실상 표준이 됐다. Redux로 모든 상태를 관리하던 시대는 끝났다. 서버 상태와 클라이언트 상태를 분리하는 것이 첫 번째 설계 결정이다.

Zustand — 가장 실용적인 선택

Zustand는 번들 크기 1.1KB(gzip), 보일러플레이트 최소, React 외부에서도 사용 가능한 경량 상태 관리 라이브러리다. Jotai와 함께 pmndrs(Poimandres) 그룹이 관리하며, npm 주간 다운로드 700만 이상으로 Redux Toolkit을 추월했다(2026년 4월 기준).

Zustand의 핵심 특징:

  • 단일 스토어, 슬라이스 패턴: 하나의 create()로 스토어를 만들고, 관심사별로 슬라이스를 분리
  • 선택적 구독: 셀렉터 함수로 필요한 상태만 구독해서 불필요한 리렌더링 방지
  • 미들웨어: persist(로컬스토리지), devtools(Redux DevTools 연동), immer(불변 업데이트) 내장
  • React 외부 사용: getState(), subscribe()로 프레임워크 외부에서 상태 접근 가능
Zustand 스토어 예시 — 쇼핑 카트
import { create } from 'zustand' import { persist, devtools } from 'zustand/middleware' import { immer } from 'zustand/middleware/immer' interface CartItem { id: string name: string price: number quantity: number } interface CartStore { items: CartItem[] addItem: (item: Omit<CartItem, 'quantity'>) => void removeItem: (id: string) => void updateQuantity: (id: string, quantity: number) => void totalPrice: () => number clearCart: () => void } export const useCartStore = create<CartStore>()( devtools( persist( immer((set, get) => ({ items: [], addItem: (item) => set((state) => { const existing = state.items.find((i) => i.id === item.id) if (existing) { existing.quantity += 1 } else { state.items.push({ ...item, quantity: 1 }) } }), removeItem: (id) => set((state) => { state.items = state.items.filter((i) => i.id !== id) }), updateQuantity: (id, quantity) => set((state) => { const item = state.items.find((i) => i.id === id) if (item) item.quantity = Math.max(0, quantity) }), totalPrice: () => get().items.reduce((sum, i) => sum + i.price * i.quantity, 0), clearCart: () => set({ items: [] }), })), { name: 'cart-storage' } ) ) ) // 컴포넌트에서 사용 — 선택적 구독 function CartBadge() { const itemCount = useCartStore((s) => s.items.length) return <span>{itemCount}</span> } function CartTotal() { const totalPrice = useCartStore((s) => s.totalPrice()) return <span>{totalPrice.toLocaleString()}원</span> }

Jotai — 아토믹 상태 관리의 정석

Jotai는 아톰(atom) 단위로 상태를 관리한다. Recoil에서 영감을 받았지만, 키 문자열 없이 객체 참조로 아톰을 식별하므로 API가 더 간결하다. 번들 크기 3.4KB(gzip).

Zustand와의 핵심 차이:

  • Zustand: 하나의 스토어에 모든 상태를 모아두는 하향식(top-down) 접근
  • Jotai: 개별 아톰을 필요한 곳에서 조합하는 상향식(bottom-up) 접근

컴포넌트가 많고 상태 간 의존성이 복잡한 대시보드, 폼 빌더, 에디터 같은 앱에서 Jotai가 유리하다. 반면 단순한 전역 상태(인증, 테마, 장바구니)는 Zustand가 더 직관적이다.

Jotai 아톰 조합 예시 — 필터가 있는 목록
import { atom, useAtom, useAtomValue } from 'jotai' // 기본 아톰 const todosAtom = atom<Todo[]>([]) const filterAtom = atom<'all' | 'active' | 'done'>('all') const searchAtom = atom('') // 파생 아톰 — 필터 + 검색 조합 const filteredTodosAtom = atom((get) => { const todos = get(todosAtom) const filter = get(filterAtom) const search = get(searchAtom).toLowerCase() return todos .filter((t) => { if (filter === 'active') return !t.done if (filter === 'done') return t.done return true }) .filter((t) => t.title.toLowerCase().includes(search)) }) // 통계 아톰 — todosAtom이 바뀔 때만 재계산 const statsAtom = atom((get) => { const todos = get(todosAtom) return { total: todos.length, done: todos.filter((t) => t.done).length, active: todos.filter((t) => !t.done).length, } }) // 비동기 아톰 — API 연동 const userAtom = atom(async () => { const res = await fetch('/api/user') return res.json() }) // 컴포넌트에서 사용 function TodoList() { const filtered = useAtomValue(filteredTodosAtom) return filtered.map((t) => <TodoItem key={t.id} todo={t} />) } function Stats() { const { total, done } = useAtomValue(statsAtom) return <p>{done}/{total} 완료</p> }
React state management library comparison chart
React 상태 관리 라이브러리 아키텍처 비교 — 스토어 vs 아톰 vs 리듀서

Redux Toolkit — 여전히 유효한 경우

Redux는 '구식'이라는 인식이 있지만, Redux Toolkit(RTK)은 여전히 특정 상황에서 최선의 선택이다:

  • 대규모 팀 (10명+ 프론트엔드): 엄격한 단방향 데이터 흐름과 액션 로깅이 디버깅과 온보딩에 유리
  • 복잡한 상태 전이 로직: createSlice + extraReducers로 서버 응답에 따른 다단계 상태 변경을 명시적으로 관리
  • RTK Query 활용: 서버 상태 캐싱을 Redux 생태계 안에서 처리하고 싶을 때 (TanStack Query 대안)
  • 기존 Redux 코드베이스: 이미 Redux가 깊이 통합된 프로젝트에서 마이그레이션 비용이 전환 이점보다 클 때

Redux Toolkit을 권장하지 않는 경우:

  • 프론트엔드 팀이 1~3명인 소규모 프로젝트
  • 상태 대부분이 서버 데이터인 CRUD 앱 (TanStack Query가 더 적합)
  • 프로토타입이나 MVP 단계 (Zustand가 빠르게 구현 가능)
  • 신규 프로젝트에서 처음 선택할 때 (학습 비용 대비 이점이 적다)

4대 라이브러리 비교표

항목ZustandJotaiRedux ToolkitValtio
번들 크기1.1KB3.4KB11KB3.8KB
패러다임단일 스토어아토믹Flux/리듀서프록시 기반
보일러플레이트최소최소중간최소
TypeScript우수우수우수좋음
DevToolsRedux DevTools자체 DevToolsRedux DevToolsValtio DevTools
SSR 지원수동 처리Provider 기반Provider 기반Snapshot 기반
React 외부 사용가능제한적가능가능
추천 규모소~대중~대대규모소~중

프로젝트별 선택 가이드 — 의사결정 플로차트

프로젝트 규모별 추천:

  • 사이드 프로젝트 / MVP: Zustand 단독. 5분 안에 셋업 가능하고, 규모가 커져도 문제없다.
  • 중규모 SaaS (팀 3~5명): Zustand(클라이언트 상태) + TanStack Query(서버 상태). 가장 보편적인 2026년 조합.
  • 대시보드 / 에디터 (상태 의존성 복잡): Jotai. 수십 개의 독립적 상태가 서로 파생되는 구조에 최적화.
  • 엔터프라이즈 / 대규모 팀 (10명+): Redux Toolkit + RTK Query. 엄격한 패턴이 대규모 팀의 코드 일관성을 보장한다.
  • Vue/Svelte에서 React로 전환: Valtio. 프록시 기반이라 Vue의 reactive()와 사고 모델이 비슷하다.

상태 관리에서 가장 흔한 실수 3가지:

  1. 서버 상태를 전역 스토어에 넣는 것: API 응답 데이터를 Redux/Zustand에 넣고 수동으로 캐싱·무효화하면 코드가 급격히 복잡해진다. TanStack Query를 쓰면 캐싱·재검증·에러 처리가 자동화된다.
  2. 너무 이른 도입: useState + useContext로 충분한 단계에서 Zustand를 추가하면 불필요한 추상화 계층이 생긴다. Context 성능 문제가 실제로 발생한 후에 도입해도 늦지 않다.
  3. 스토어를 하나의 거대한 객체로 만드는 것: Zustand에서 모든 상태를 하나의 create()에 넣으면 관심사 분리가 안 된다. 기능별로 슬라이스를 분리하거나, 별도의 스토어를 생성하라.
React state management decision flowchart
프로젝트 규모와 복잡도에 따른 상태 관리 라이브러리 선택 플로차트
ReactZustandJotaiRedux ToolkitValtio상태관리TanStack Query프론트엔드TypeScriptReact Query

관련 도구

관련 포스트

Vercel AI SDK 6 완전 가이드 — 에이전트 1급 추상화, MCP 풀 지원, DevTools2026-04-17Svelte 5 vs React 19 — 2026년 프론트엔드 프레임워크 비교2026-03-13Playwright E2E 테스트 자동화 실전 가이드 — 설치부터 CI/CD 통합까지2026-04-03TypeScript 6.0 마이그레이션 가이드 — strict 기본값화, ESM 전환, Go 컴파일러 전환 준비2026-04-05