2025년에 상태 관리가 필요할까요? React Context vs Zustand vs Jotai vs Redux

발행: (2025년 12월 5일 오전 03:43 GMT+9)
10 min read
원문: Dev.to

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 상태와는 구분

📊 정면 비교

빠른 기능 매트릭스

기능ContextZustandJotaiRedux ToolkitTanStack Query
번들 크기0 KB1.2 KB3 KB15 KB13 KB
학습 곡선1 시간2 시간4 시간2 일3 시간
TypeScript✅ 훌륭✅ 훌륭✅ 훌륭✅ 뛰어남✅ 뛰어남
DevTools❌ 없음✅ 미들웨어 통해✅ 원자 통해✅ Redux DevTools✅ 내장
미들웨어❌ 없음✅ 지원✅ 지원✅ 광범위⚠️ 플러그인
비동기 액션⚠️ 수동✅ 쉬움✅ 쉬움✅ RTK Query✅ 내장
영속성⚠️ 수동✅ 미들웨어 통해✅ 원자를 통해✅ 미들웨어 통해✅ 내장
성능⚠️ 재렌더링 가능✅ 최적화✅ 원자✅ 최적화✅ 최적화
보일러플레이트✅ 최소✅ 최소✅ 최소❌ 중간✅ 최소
타임 트래블❌ 없음⚠️ 미들웨어로 가능⚠️ 도구로 가능✅ 내장❌ 없음

성능 벤치마크

1 000번 상태 업데이트와 10개의 구독 컴포넌트를 테스트했습니다:

솔루션업데이트 시간재렌더링 횟수메모리 사용량
Context (비최적화)127 ms10 0002.1 MB
Context (최적화)89 ms1 0002.0 MB
Zustand67 ms1 0002.3 MB
Jotai71 ms1 0002.5 MB
Redux Toolkit84 ms1 0003.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;
};
Back to Blog

관련 글

더 보기 »

전체 Redux 내부

Redux 내부 흐름 다이어그램 텍스트 ┌─────────────────────────────┐ │ Your Component │ │ dispatch action │ └──────────────┬──────────────...

Dev 커뮤니티 신규 회원

여러분 안녕하세요, 저는 dev 커뮤니티에 새로 온 사람이고 코딩 여정을 다시 시작하고 있습니다. 저는 2013년부터 2018년까지 코딩을 했었습니다. 그 이후에 새로운 기회를 탐색했고, st...