본문으로 바로가기
기술

상태관리 라이브러리 비교 분석 — Redux, Zustand, Jotai, Recoil 실전 선택 가이드

A
AlwaysCorp 기술팀· 프론트엔드 개발 콘텐츠 전문
||14분 읽기
#상태관리#Redux#Zustand#Jotai#Recoil#React#프론트엔드#TypeScript#아키텍처

"최고의 상태관리 라이브러리"는 없다

"React 프로젝트에 어떤 상태관리를 써야 하나요?" — 프론트엔드 커뮤니티에서 가장 자주 올라오는 질문 중 하나입니다. 그런데 이 질문에 대한 정답은 "프로젝트에 따라 다르다"는 다소 실망스러운 답변이죠.

npm trends 데이터를 보면 2025년 상반기 기준 Redux가 여전히 주간 다운로드 900만 회 이상으로 압도적 1위를 차지하고 있지만, Zustand(주간 600만 회)의 성장세가 가파르고, Jotai(주간 200만 회)도 꾸준히 사용자를 확보하고 있습니다. 시장이 다변화되고 있다는 신호이니, 각 라이브러리의 특성을 이해하고 프로젝트에 맞는 선택을 하는 것이 중요합니다.

---

설계 철학의 차이부터 이해하자

Flux 패턴 vs Atomic 패턴

상태관리 라이브러리는 크게 두 가지 패러다임으로 나뉩니다.

Flux/Store 패턴 (Redux, Zustand): 하나의 중앙 스토어에 상태를 모아두고, 정해진 방법(action/dispatch)으로만 상태를 변경합니다. 데이터 흐름이 예측 가능하고, 상태 변화의 히스토리를 추적하기 쉬운 것이 장점입니다.

Atomic 패턴 (Jotai, Recoil): 상태를 독립적인 원자(atom) 단위로 쪼개서 관리합니다. 필요한 atom만 구독하므로 불필요한 리렌더링이 최소화되고, 컴포넌트 중심의 상향식(bottom-up) 설계와 궁합이 좋습니다.

상태관리 라이브러리 패러다임 비교 — Flux vs Atomic
상태관리 라이브러리 패러다임 비교 — Flux vs Atomic

---

Redux Toolkit — 성숙한 생태계의 힘

Redux는 2015년 등장 이래 React 상태관리의 사실상 표준으로 자리잡았습니다. 초기에는 보일러플레이트가 많다는 비판을 받았지만, Redux Toolkit(RTK)이 이 문제를 상당 부분 해소했죠.

```typescript import { createSlice, configureStore } from '@reduxjs/toolkit';

const todosSlice = createSlice({ name: 'todos', initialState: [] as Todo[], reducers: { addTodo: (state, action: PayloadAction<string>) => { // Immer 내장 — 불변성을 "뮤터블하게" 작성 가능 state.push({ id: crypto.randomUUID(), text: action.payload, done: false }); }, toggleTodo: (state, action: PayloadAction<string>) => { const todo = state.find(t => t.id === action.payload); if (todo) todo.done = !todo.done; }, }, });

const store = configureStore({ reducer: { todos: todosSlice.reducer }, });

// RTK Query — 서버 상태 관리 통합 const api = createApi({ baseQuery: fetchBaseQuery({ baseUrl: '/api' }), endpoints: (builder) => ({ getTodos: builder.query<Todo[], void>({ query: () => '/todos', }), }), }); ```

Redux를 선택해야 할 때: 대규모 팀 프로젝트, 복잡한 비즈니스 로직, 시간 여행 디버깅이 필요한 경우, 미들웨어(로깅, 분석, 동기화)가 다수 필요한 경우.

Redux의 단점: 소규모 프로젝트에서는 과도한 구조화(over-engineering)가 될 수 있습니다. Provider, store, slice, selector 등 알아야 할 개념이 많아 학습 곡선도 존재하고요.

---

Zustand — 미니멀리스트의 선택

Zustand(독일어로 "상태")는 Jotai를 만든 Daishi Kato가 설계한 라이브러리로, 최소한의 API로 강력한 상태관리를 제공합니다.

```typescript import { create } from 'zustand'; import { devtools, persist } from 'zustand/middleware';

interface TodoStore { todos: Todo[]; addTodo: (text: string) => void; toggleTodo: (id: string) => void; doneTodos: () => Todo[]; }

const useTodoStore = create<TodoStore>()( devtools( persist( (set, get) => ({ todos: [], addTodo: (text) => set((state) => ({ todos: [...state.todos, { id: crypto.randomUUID(), text, done: false }], })), toggleTodo: (id) => set((state) => ({ todos: state.todos.map(t => t.id === id ? { ...t, done: !t.done } : t), })), doneTodos: () => get().todos.filter(t => t.done), }), { name: 'todo-store' } // localStorage 키 ) ) );

// 컴포넌트에서 사용 — Provider 불필요! function TodoList() { const todos = useTodoStore((state) => state.todos); const addTodo = useTodoStore((state) => state.addTodo); // ... } ```

Zustand의 가장 매력적인 점은 Provider가 필요 없다는 것입니다. React 컴포넌트 트리 외부에서도 상태에 접근할 수 있어, 유틸리티 함수나 API 인터셉터에서 직접 스토어를 읽고 쓸 수 있습니다.

```typescript // React 외부에서 상태 접근 const currentTodos = useTodoStore.getState().todos; useTodoStore.setState({ todos: [...currentTodos, newTodo] }); ```

Zustand를 선택해야 할 때: 중소규모 프로젝트, 간단한 전역 상태, 빠른 프로토타이핑, Provider 지옥을 피하고 싶을 때.

---

Jotai — 원자 단위의 정밀한 제어

Jotai(일본어로 "상태")는 Recoil에서 영감을 받되, 더 간결한 API를 목표로 설계되었습니다. 키 문자열이 필요 없고, TypeScript 추론이 자연스럽죠.

```typescript import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai';

// 기본 atom const todosAtom = atom<Todo[]>([]);

// 파생 atom — 자동으로 의존성 추적 const doneTodosAtom = atom((get) => { return get(todosAtom).filter(t => t.done); });

const todoCountAtom = atom((get) => get(todosAtom).length);

// 쓰기 전용 atom const addTodoAtom = atom(null, (get, set, text: string) => { const todos = get(todosAtom); set(todosAtom, [...todos, { id: crypto.randomUUID(), text, done: false }]); });

// 비동기 atom const userAtom = atom(async () => { const res = await fetch('/api/user'); return res.json(); });

function TodoList() { const todos = useAtomValue(todosAtom); const doneTodos = useAtomValue(doneTodosAtom); const addTodo = useSetAtom(addTodoAtom); // doneTodos는 todosAtom이 변경될 때만 재계산 } ```

Jotai의 핵심 강점은 구독 최적화입니다. 각 컴포넌트는 자신이 사용하는 atom만 구독하므로, 다른 atom이 변경되어도 리렌더링이 발생하지 않습니다. Redux에서 selector를 잘못 작성하면 불필요한 리렌더링이 발생하는 문제가 Jotai에서는 구조적으로 방지됩니다.

Jotai를 선택해야 할 때: 리렌더링 최적화가 중요한 경우, 파생 상태가 많은 복잡한 데이터 모델, React Suspense와의 통합이 필요한 경우.

---

Recoil — 페이스북의 실험

Recoil은 Meta(구 페이스북)에서 만든 상태관리 라이브러리입니다. Atom + Selector 패턴의 선구자이지만, 2024년 이후 업데이트가 뜸해져 커뮤니티에서 우려의 목소리가 있는 상태입니다.

```typescript import { atom, selector, useRecoilState, useRecoilValue } from 'recoil';

const todosState = atom<Todo[]>({ key: 'todosState', // 전역 고유 키 (필수) default: [], });

const doneTodosState = selector({ key: 'doneTodosState', get: ({ get }) => get(todosState).filter(t => t.done), }); ```

Recoil은 Jotai와 유사한 패턴이지만, 모든 atom과 selector에 전역 고유 키가 필요하다는 점이 번거롭습니다. 또한 실험적(`experimental`) 태그가 오래 유지되고 있어 프로덕션 도입에 신중해야 할 수 있습니다.

상태관리 라이브러리 선택 플로차트 — 프로젝트 특성별 추천
상태관리 라이브러리 선택 플로차트 — 프로젝트 특성별 추천

---

성능 비교 — 리렌더링과 번들 크기

번들 크기(gzip 기준)를 비교하면, Zustand(약 1.1KB)와 Jotai(약 2.4KB)가 Redux Toolkit(약 11KB) 대비 훨씬 가볍습니다. 물론 RTK는 Immer, RTK Query 등을 포함하고 있어 단순 비교는 공정하지 않지만, 번들 크기에 민감한 프로젝트에서는 고려 사항이 됩니다.

리렌더링 측면에서는 Jotai가 가장 정밀한 구독 모델을 제공하고, Zustand는 selector 기반 구독으로 최적화하며, Redux는 `createSelector`(reselect)를 통한 메모이제이션에 의존합니다.

---

서버 상태는 별도로 관리하라

React Query(TanStack Query)의 등장 이후, "모든 상태를 하나의 라이브러리로 관리한다"는 패러다임이 변화했습니다. 서버에서 가져오는 데이터(API 응답, 캐시)와 클라이언트 UI 상태(모달 열림, 탭 선택)를 분리하는 것이 현대적인 접근법입니다.

서버 상태를 TanStack Query로 관리하면, 클라이언트 상태관리 라이브러리가 담당해야 할 범위가 크게 줄어듭니다. 그 결과 Zustand이나 Jotai처럼 가벼운 라이브러리만으로도 충분한 경우가 많아졌죠.

상태관리 라이브러리의 선택은 기술적 우열이 아니라 팀과 프로젝트의 맥락에 따라 결정되어야 합니다. 3인 이하 소규모 팀에서 MVP를 만든다면 Zustand, 리렌더링 최적화가 핵심이라면 Jotai, 대규모 팀에서 엄격한 아키텍처가 필요하다면 Redux Toolkit이 각각 최선의 선택이 될 것입니다.
A

AlwaysCorp 기술팀

프론트엔드 개발 콘텐츠 전문

얼웨이즈 블로그에서 유용한 정보와 인사이트를 공유합니다.