- 공유 링크 만들기
- X
- 이메일
- 기타 앱
- 공유 링크 만들기
- X
- 이메일
- 기타 앱
React 상태관리 비교 2026
Zustand vs Jotai vs Redux: React 상태관리 라이브러리 완전 비교
경량화 vs 원자화 vs 강력함 — 2026년 팅패는 무엇인가?
React 상태관리 라이브러리를 선택할 때 가장 많이 비교되는 세 가지가 바로 Zustand, Jotai, Redux Toolkit입니다. 10년 넘게 Redux를 써온 팀도 있고, 2년 전부터 Zustand로 갈아탄 팀도 있습니다. 2026년 기준으로 실제 프로젝트에서 세 가지를 모두 써본 경험을 바탕으로 정리합니다.
🔍 한눈에 보는 Zustand vs Jotai vs Redux 비교표
| 비교 항목 | Zustand | Jotai | Redux Toolkit |
|---|---|---|---|
| 번들 크기 | ~1KB | ~3KB | ~11KB |
| 학습 곡선 | 매우 낮음 ✅ | 낮음 ✅ | 보통 ⚠️ |
| 보일러플레이트 | 최소 ✅ | 최소 ✅ | 많음 ⚠️ |
| 상태 모델 | 중앙 집중 Store | 원자(Atom) 단위 | 중앙 집중 Store |
| 리렌더링 최적화 | 선택적 구독 | 원자 단위 자동 | 선택자 기반 |
| TypeScript 지원 | 우수 ✅ | 우수 ✅ | 우수 ✅ |
| DevTools | 기본 지원 | Jotai DevTools | Redux DevTools ✅ |
| 미들웨어/플러그인 | immer, persist 등 | jotai-tanstack, zubridge 등 | RTK Query, Thunk ✅ |
| 비동기 처리 | 직접 구현 | atomWithAsync | RTK Query / Thunk |
| 추천 사용 규모 | 소~중형 | 소~중형 | 중~대형 |
| npm 주간 다운로드 | ~9M | ~2.5M | ~10M+ |
📦 번들 크기 & 보일러플레이트 비교
번들 크기는 프론트엔드 성능에 직결됩니다. 특히 모바일 환경에서는 1KB 차이도 체감이 납니다.
| 라이브러리 | minified+gzip | 설치 패키지 수 | 카운터 앱 구현 코드 줄수 |
|---|---|---|---|
| Zustand | ~1.1KB | 1개 | ~8줄 |
| Jotai | ~3.2KB | 1개 | ~6줄 |
| Redux Toolkit | ~11KB | 2개 (redux + toolkit) | ~25줄 |
🐻 Zustand 실전 코드
Zustand는 설정이 거의 없습니다. store를 하나의 훅으로 정의하고 바로 사용합니다.
// store/useCounterStore.ts
import { create } from 'zustand'
interface CounterState {
count: number
increment: () => void
decrement: () => void
reset: () => void
}
export const useCounterStore = create<CounterState>()(set => ({
count: 0,
increment: () => set(state => ({ count: state.count + 1 })),
decrement: () => set(state => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
}))
// 컴포넌트에서 사용
export function Counter() {
const { count, increment, decrement } = useCounterStore()
return (
<div>
<button onClick={decrement}>-</button>
<span>{count}</span>
<button onClick={increment}>+</button>
</div>
)
}
선택적 구독으로 리렌더링을 제어할 수도 있습니다:
// count 값만 구독 — 다른 state 변경 시 리렌더링 없음
const count = useCounterStore(state => state.count)
// persist 미들웨어로 localStorage 동기화
import { persist } from 'zustand/middleware'
const useStore = create()(persist(
(set) => ({ count: 0, increment: () => set(s => ({ count: s.count + 1 })) }),
{ name: 'counter-storage' }
))
⚛️ Jotai 실전 코드
Jotai는 React의 useState와 가장 비슷한 API를 제공합니다. 원자(atom) 단위로 상태를 정의하고, 필요한 곳에서 useAtom으로 사용합니다.
// atoms/counterAtom.ts
import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai'
export const countAtom = atom(0)
// 파생 atom (computed state)
export const doubleCountAtom = atom((get) => get(countAtom) * 2)
// 컴포넌트에서 사용
export function Counter() {
const [count, setCount] = useAtom(countAtom)
const doubleCount = useAtomValue(doubleCountAtom)
return (
<div>
<p>count: {count} / double: {doubleCount}</p>
<button onClick={() => setCount(c => c + 1)}>+</button>
</div>
)
}
// atomWithStorage로 localStorage 연동
import { atomWithStorage } from 'jotai/utils'
const darkModeAtom = atomWithStorage('darkMode', false)
🔴 Redux Toolkit 실전 코드
Redux Toolkit은 보일러플레이트가 많지만, 대형 앱에서는 이 구조가 오히려 팀 협업에 유리합니다.
// store/counterSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
interface CounterState { value: number }
const initialState: CounterState = { value: 0 }
export const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increment: (state) => { state.value += 1 },
decrement: (state) => { state.value -= 1 },
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value += action.payload
},
},
})
export const { increment, decrement, incrementByAmount } = counterSlice.actions
export default counterSlice.reducer
// store/index.ts
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from './counterSlice'
export const store = configureStore({
reducer: { counter: counterReducer }
})
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
// 컴포넌트에서 사용
import { useDispatch, useSelector } from 'react-redux'
function Counter() {
const count = useSelector((state: RootState) => state.counter.value)
const dispatch = useDispatch<AppDispatch>()
return <button onClick={() => dispatch(increment())}>{count}</button>
}
⚡ 성능 & 리렌더링 비교
| 시나리오 | Zustand | Jotai | Redux Toolkit |
|---|---|---|---|
| 단순 카운터 업데이트 | ~0.1ms | ~0.1ms | ~0.2ms |
| 1,000개 아이템 리스트 렌더링 | 선택적 구독으로 우수 | atom 분리로 최적 | selector 튜닝 필요 |
| 글로벌 상태 업데이트 시 리렌더링 범위 | 구독 컴포넌트만 | 해당 atom 구독 컴포넌트만 | selector 없으면 전체 |
| 초기 번들 로드 영향 | 거의 없음 | 거의 없음 | 약간 있음 |
| 메모리 사용량 | 낮음 | 낮음 | 보통 |
🛠️ DevTools & 생태계 비교
| 항목 | Zustand | Jotai | Redux Toolkit |
|---|---|---|---|
| DevTools 브라우저 확장 | Redux DevTools 연동 가능 | Jotai DevTools (별도 설치) | Redux DevTools ✅ 최고 |
| 타임트래블 디버깅 | 미들웨어로 가능 | 제한적 | 완벽 지원 ✅ |
| React Native 지원 | ✅ | ✅ | ✅ |
| 서버 컴포넌트(RSC) 지원 | 클라이언트 전용 | 클라이언트 전용 | 클라이언트 전용 |
| GitHub Stars (2026 기준) | ~51K | ~20K | ~11K (RTK) / redux ~61K |
🎯 어떤 상황에 뭘 써야 할까?
🐻 Zustand를 선택하세요
- 빠르게 MVP를 만들어야 할 때
- 팀 전체가 Redux에 지쳐 있을 때
- 상태 구조가 단순한 소~중형 앱
- Next.js App Router와 함께 쓸 때
- localStorage 동기화가 필요할 때
⚛️ Jotai를 선택하세요
- 컴포넌트 단위 상태 관리가 필요할 때
- 파생 상태(computed state)가 복잡할 때
- React Suspense와 자연스럽게 쓰고 싶을 때
- useState의 스케일업 버전이 필요할 때
- 상태를 atom으로 분리해 테스트하고 싶을 때
🔴 Redux Toolkit을 선택하세요
- 5인 이상 팀이 협업하는 대형 앱
- 시간여행 디버깅이 필수일 때
- RTK Query로 서버 상태 통합 관리
- 레거시 Redux 코드베이스 유지보수
- 엄격한 아키텍처 컨벤션이 필요할 때
| 항목 | |||
|---|---|---|---|
| 번들 크기 | 매우 작음 (~2KB) | 작음 (~4KB) | 크다 (~20KB) |
| 학습 곡선 | 매우 낮음 | 낮음 | 높음 |
| 성능 | 우수 | 우수 | 좋음 |
| DevTools | 기본 지원 | 설정 필요 | 강력함 |
| 타입 안정성 | 좋음 | 우수 | 우수 |
| 미들웨어 | 간단 | 간단 | 복잡 |
| 에코시스템 | 성장 중 | 새로움 | 매우 큼 |
🏆 최종 결론
2026년 Jake의 추천
✅ 신규 프로젝트 · 스타트업 · 1~4인 팀 → Zustand
API가 가장 단순하고, 생산성이 뛰어납니다. 99%의 케이스에서 충분합니다.
✅ 복잡한 파생 상태 · React Suspense 활용 → Jotai
atom 모델이 복잡한 상태 의존 관계를 깔끔하게 정리해줍니다.
✅ 5인 이상 팀 · 엔터프라이즈 · 레거시 코드 → Redux Toolkit
DevTools, 타임트래블, RTK Query가 대형 앱에서 진가를 발휘합니다.
솔직히 말하면, 2026년 신규 프로젝트라면 저는 무조건 Zustand를 선택합니다. 배우는 데 30분도 안 걸리고, 나중에 복잡해지면 Redux로 마이그레이션하는 것보다 처음부터 Zustand로 시작해서 구조를 잡는 게 훨씬 빠릅니다.
❓ 자주 묻는 질문 (FAQ)
Q. Zustand와 Jotai를 같이 써도 되나요?
네, 가능합니다. 전역 앱 상태(테마, 인증)는 Zustand, 컴포넌트 간 공유 상태는 Jotai로 분리해 쓰는 팀도 있습니다. 다만 두 라이브러리가 혼재하면 팀 내 혼란이 생길 수 있으니, 명확한 사용 기준을 정하는 것이 중요합니다.
Q. Redux에서 Zustand로 마이그레이션하는 게 어렵나요?
생각보다 어렵지 않습니다. 핵심은 Redux의 slice를 Zustand의 create()로 1:1 변환하는 것입니다. RTK Query를 쓰고 있다면 TanStack Query로 교체하는 것을 함께 고려하세요. 한 번에 전체를 마이그레이션하기보다, 새 기능은 Zustand로 작성하고 기존 코드는 점진적으로 교체하는 방식을 추천합니다.
Q. Next.js App Router에서 상태관리 라이브러리를 써도 되나요?
Zustand, Jotai, Redux 모두 클라이언트 컴포넌트('use client')에서만 동작합니다. 서버 컴포넌트에서는 사용할 수 없으며, 서버 상태는 TanStack Query나 Next.js의 fetch 캐싱을 활용하는 것이 권장됩니다. Next.js App Router에서는 전역 클라이언트 상태를 최소화하는 방향으로 설계하는 것이 좋습니다.
Q. React Context와 비교했을 때 언제 상태관리 라이브러리가 필요한가요?
React Context는 자주 변경되지 않는 데이터(테마, 언어 설정, 인증 정보)에 적합합니다. 반면 초당 여러 번 업데이트되는 상태(폼, 실시간 데이터, 쇼핑카트)는 Context의 전체 리렌더링 문제가 성능 이슈로 이어집니다. 이 경우 Zustand나 Jotai가 훨씬 효율적입니다.
🔗 관력 포스트
댓글
댓글 쓰기