2025년에 상태 관리가 필요할까요? React Context vs Zustand vs Jotai vs Redux
Source: Dev.to
🎯 문제 정의
배경
- 포트폴리오 사이트: 개인 브랜드, 블로그, 프로젝트 전시
- UI 라이브러리: 25개 이상의 재사용 가능한 React 컴포넌트
- 상태 요구사항: 테마, 네비게이션, 폼, 분석
- 팀 규모: 1인 개발자 (빠른 반복 필요)
- 제약 조건: 과도한 설계 금지, 명확한 업그레이드 경로
- 미래: 전자상거래 기능, 사용자 계정, 복잡한 데이터
도전 과제
잘못된 상태 관리 솔루션 선택은 다음과 같은 문제를 일으킬 수 있습니다:
- 🐌 과도한 설계: 3개의 상태만 관리하는데 Redux 사용 = 과잉 설계
- 🔄 부족한 설계: 실시간 피드에 Context 사용 = 성능 문제
- 📚 학습 곡선: 새로운 개발자가 패턴을 이해해야 함
- 🔧 마이그레이션 비용: 잘못된 선택 = 나중에 2~3일 동안 리팩터링 필요
- 💰 번들 크기: 일부 솔루션은 번들에 15 KB 이상을 추가
왜 이 결정이 중요한가
- ⏱️ 개발자 속도: 간단한 상태 관리 = 빠른 기능 개발
- 🚀 성능: 올바른 도구가 재렌더링 문제를 방지
- 🔄 확장성: 복잡도가 증가함에 따라 명확한 업그레이드 경로 필요
- 🤝 팀 온보딩: 미래 팀이 빠르게 이해할 수 있어야 함
- 📦 번들 크기: 성능을 위해 KB 단위도 중요
✅ 평가 기준
필수 요구사항
- TypeScript 지원 – 상태에 대한 완전한 타입 안전성
- 간단한 API – 이해하고 가르치기 쉬움
- 성능 – 불필요한 재렌더링 방지
- DevTools – 상태 변화를 디버깅할 수 있음
- React 19 호환 – 최신 React와 동작
있으면 좋은 기능
- 타임 트래블 디버깅 (Redux DevTools)
- 미들웨어 지원 (로그, 영속성)
- 비동기 액션 처리
- 옵티미스틱 업데이트
- 상태 영속성 (localStorage)
- 서버 상태 통합
절대 포기할 수 없는 요소
- ❌ 간단한 상태에 과도한 보일러플레이트 요구
- ❌ TypeScript 지원 부족
- ❌ 큰 번들 크기 (기본 기능에 10 KB 이상)
- ❌ 학습 곡선이 가파름 (이해에 2일 이상)
- ❌ 특정 아키텍처 패턴 강제
점수 매기기 프레임워크
| 기준 | 가중치 | 이유 |
|---|---|---|
| 단순성 | 30% | 1인 개발자가 빠르게 반복해야 함 |
| 성능 | 25% | 재렌더링은 UX를 죽인다 |
| 번들 크기 | 20% | 포트폴리오 사이트는 빠르게 로드돼야 함 |
| TypeScript 지원 | 15% | 타입 안전성이 버그를 방지함 |
| 확장성 | 10% | 추후 복잡한 상태가 필요할 수 있음 |
🥊 후보군
React Context + useState – 내장 솔루션
- 적합도: 간단~중간 수준 상태 필요
- 핵심 강점: 의존성 0, React 네이티브
- 핵심 약점: 내장 DevTools 없음, 재렌더링 발생 가능
- 번들 크기: 0 KB (React에 포함)
- 첫 릴리즈: React 16.3 (2018), 19에서 개선
- 유지 관리: Meta (React 팀)
- 현재 상태: 안정적, 활발히 개선 중
Zustand – 미니멀리스트 상태 관리
- 적합도: 전역 상태가 필요한 중간 복잡도 앱
- 핵심 강점: 간단한 API, 초소형 크기, 뛰어난 개발자 경험
- 핵심 약점: Redux보다 구조화가 덜 됨
- 번들 크기: 1.2 KB gzipped
- GitHub 스타: 50.5k ⭐
- NPM 다운로드: 5 M/주
- 첫 릴리즈: 2019
- 유지 관리: Poimandres (pmndrs) 팀
- 현재 버전: 4.5.x (안정, 성숙)
Jotai – 원자 기반 상태 관리
- 적합도: 파생값이 많은 복잡한 상태
- 핵심 강점: 원자 업데이트, 하향식 접근법
- 핵심 약점: Redux/Context와 다른 사고 모델
- 번들 크기: 3 KB gzipped
- GitHub 스타: 18.8k ⭐
- NPM 다운로드: 1.5 M/주
- 첫 릴리즈: 2020
- 유지 관리: Poimandres (pmndrs) 팀
- 현재 버전: 2.x (안정, 활발히 개발)
Redux Toolkit – 엔터프라이즈 솔루션
- 적합도: 대규모 앱, 엄격한 구조가 필요한 팀
- 핵심 강점: 강력한 DevTools, 미들웨어, 구조화된 설계
- 핵심 약점: 코드가 장황하고 학습 곡선이 있음, 보일러플레이트 필요
- 번들 크기: 15 KB gzipped
- GitHub 스타: 47k ⭐ (Redux) + 10.8k ⭐ (RTK)
- NPM 다운로드: 10 M/주
- 첫 릴리즈: 2015 (Redux), 2019 (RTK)
- 유지 관리: Redux 팀 (Mark Erikson)
- 현재 버전: 2.x (안정, 성숙)
TanStack Query – 서버 상태 전문 도구
- 적합도: API 호출과 캐싱이 많은 앱
- 핵심 강점: 최고의 서버 상태 관리 기능
- 핵심 약점: 클라이언트 UI 상태용이 아님 (목적이 다름)
- 번들 크기: 13 KB gzipped
- GitHub 스타: 43k ⭐
- NPM 다운로드: 5 M/주
- 첫 릴리즈: 2019 (React Query)
- 유지 관리: Tanner Linsley
- 비고: 다른 카테고리 – API/서버 상태를 다루며 UI 상태와는 구분
📊 정면 비교
빠른 기능 매트릭스
| 기능 | Context | Zustand | Jotai | Redux Toolkit | TanStack Query |
|---|---|---|---|---|---|
| 번들 크기 | 0 KB | 1.2 KB | 3 KB | 15 KB | 13 KB |
| 학습 곡선 | 1 시간 | 2 시간 | 4 시간 | 2 일 | 3 시간 |
| TypeScript | ✅ 훌륭 | ✅ 훌륭 | ✅ 훌륭 | ✅ 뛰어남 | ✅ 뛰어남 |
| DevTools | ❌ 없음 | ✅ 미들웨어 통해 | ✅ 원자 통해 | ✅ Redux DevTools | ✅ 내장 |
| 미들웨어 | ❌ 없음 | ✅ 지원 | ✅ 지원 | ✅ 광범위 | ⚠️ 플러그인 |
| 비동기 액션 | ⚠️ 수동 | ✅ 쉬움 | ✅ 쉬움 | ✅ RTK Query | ✅ 내장 |
| 영속성 | ⚠️ 수동 | ✅ 미들웨어 통해 | ✅ 원자를 통해 | ✅ 미들웨어 통해 | ✅ 내장 |
| 성능 | ⚠️ 재렌더링 가능 | ✅ 최적화 | ✅ 원자 | ✅ 최적화 | ✅ 최적화 |
| 보일러플레이트 | ✅ 최소 | ✅ 최소 | ✅ 최소 | ❌ 중간 | ✅ 최소 |
| 타임 트래블 | ❌ 없음 | ⚠️ 미들웨어로 가능 | ⚠️ 도구로 가능 | ✅ 내장 | ❌ 없음 |
성능 벤치마크
1 000번 상태 업데이트와 10개의 구독 컴포넌트를 테스트했습니다:
| 솔루션 | 업데이트 시간 | 재렌더링 횟수 | 메모리 사용량 |
|---|---|---|---|
| Context (비최적화) | 127 ms | 10 000 | 2.1 MB |
| Context (최적화) | 89 ms | 1 000 | 2.0 MB |
| Zustand | 67 ms | 1 000 | 2.3 MB |
| Jotai | 71 ms | 1 000 | 2.5 MB |
| Redux Toolkit | 84 ms | 1 000 | 3.1 MB |
핵심 인사이트: 최적화된 Context는 Zustand만큼 빠르지만, 더 많은 수동 최적화 작업이 필요합니다.
2025년 상태 관리 풍경
- React Context +
useState/useReducer– React에 내장, 의존성 0, 중간 수준 상태에 최적. - Zustand – ≈1 KB, 간단한 API, 훅 기반, 뛰어난 개발자 경험.
- Jotai – 원자 기반, 하향식 접근, Recoil 영감을 받았지만 더 단순.
- Redux Toolkit – 업계 표준, 강력한 DevTools, 구조화되었지만 장황함.
- TanStack Query – 서버‑상태 전문 (다른 카테고리, UI 상태 도구와 혼동되는 경우 많음).
진짜 질문은 “어떤 것이 최고인가?”가 아니라 “내 앱에 실제로 필요한 복잡도 수준은 어느 정도인가?” 입니다.
왜 React Context부터 시작했는가
내 포트폴리오 사이트는 상태 조각이 몇 개에 불과합니다:
- 테마 설정 (라이트/다크 모드)
- 네비게이션 상태 (모바일 메뉴 열림/닫힘)
- 폼 상태 (연락처 폼, 뉴스레터 구독)
- 분석 추적 (사용자 인터랙션)
복잡한 데이터 흐름도 없고, 동일한 상태를 공유해야 하는 깊게 중첩된 컴포넌트 트리도 없으며, 전역 캐시 동기화도 필요 없습니다. React Context가 이를 아름답게 처리합니다:
// contexts/ThemeContext.tsx
import { createContext, useContext, useState, ReactNode } from 'react';
type Theme = 'light' | 'dark';
interface ThemeContextType {
theme: Theme;
toggleTheme: () => void;
}
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
export const ThemeProvider = ({ children }: { children: ReactNode }) => {
const [theme, setTheme] = useState<Theme>('light');
const toggleTheme = () => setTheme(prev => (prev === 'light' ? 'dark' : 'light'));
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
export const useTheme = () => {
const ctx = useContext(ThemeContext);
if (!ctx) throw new Error('useTheme must be used within ThemeProvider');
return ctx;
};