Zustand는 10줄로 Redux를 대체하는 무료 상태 관리자

발행: (2026년 3월 30일 오전 03:10 GMT+9)
5 분 소요
원문: Dev.to

Source: Dev.to

Redux는 종종 액션, 리듀서, 셀렉터, 미들웨어, 그리고 간단한 카운터조차도 수십 줄의 보일러플레이트 코드를 요구합니다.
Zustand는 동일한 기능을 단 몇 줄만으로 제공하며, 액션, 리듀서, 디스패처, Provider 컴포넌트가 전혀 필요 없습니다.

Minimal API

  • 몇 줄만으로 스토어를 만들고 어디서든 사용할 수 있습니다.
  • 보일러플레이트가 없습니다—액션, 리듀서, 혹은 컨텍스트 프로바이더가 필요 없습니다.
  • <Provider> 래퍼가 필요 없습니다—그냥 훅을 임포트하세요.
  • TypeScript‑first—박스 밖에서 바로 완전 타입 지원.
  • 내장 미들웨어 지원 (persist, devtools, immer, subscriptions).
  • gzipped 약 1 KB (Redux Toolkit의 약 30 KB와 비교).

설치

npm install zustand

Counter Example

import { create } from 'zustand';

interface CounterStore {
  count: number;
  increment: () => void;
  decrement: () => void;
  reset: () => void;
}

const useCounter = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  reset: () => set({ count: 0 }),
}));

// Any component can import and use the store directly
function Counter() {
  const count = useCounter((state) => state.count);
  const increment = useCounter((state) => state.increment);

  return (
    <div>
      Count: {count}
      <button onClick={increment}>+</button>
    </div>
  );
}

<Provider> 래퍼도 없고, connect()도 없으며, useSelector/useDispatch도 필요 없습니다.

지속성을 갖춘 인증 스토어

import { create } from 'zustand';
import { persist } from 'zustand/middleware';

interface User {
  id: string;
  name: string;
  email: string;
}

interface AuthStore {
  user: User | null;
  token: string | null;
  login: (email: string, password: string) => Promise;
  logout: () => void;
  isAuthenticated: () => boolean;
}

const useAuth = create()(
  persist(
    (set, get) => ({
      user: null,
      token: null,

      login: async (email, password) => {
        const { user, token } = await api.login(email, password);
        set({ user, token });
      },

      logout: () => set({ user: null, token: null }),

      isAuthenticated: () => get().token !== null,
    }),
    { name: 'auth-storage' } // Persists to localStorage
  )
);

Todo Store (비동기 액션)

import { create } from 'zustand';

interface Todo {
  id: string;
  title: string;
  done: boolean;
}

interface TodoStore {
  todos: Todo[];
  loading: boolean;
  fetchTodos: () => Promise;
  addTodo: (title: string) => Promise;
  toggleTodo: (id: string) => void;
}

const useTodos = create((set, get) => ({
  todos: [],
  loading: false,

  fetchTodos: async () => {
    set({ loading: true });
    const todos = await api.getTodos();
    set({ todos, loading: false });
  },

  addTodo: async (title) => {
    const todo = await api.createTodo({ title });
    set((state) => ({ todos: [...state.todos, todo] }));
  },

  toggleTodo: (id) => {
    set((state) => ({
      todos: state.todos.map((t) =>
        t.id === id ? { ...t, done: !t.done } : t
      ),
    }));
  },
}));

비동기 로직은 순수 async/await 로 작성되었습니다; createAsyncThunk 나 추가 미들웨어가 필요 없습니다.

미들웨어 스택 예시

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

interface Store {
  items: string[];
  addItem: (item: string) => void;
}

const useStore = create()(
  devtools(
    persist(
      immer((set) => ({
        items: [],
        addItem: (item) =>
          set((state) => {
            state.items.push(item); // Immer makes this mutable update safe
          }),
      })),
      { name: 'my-store' }
    )
  )
);

Redux DevTools 지원, localStorage 영속성, 그리고 Immer 기반의 가변 업데이트를 결합합니다.

Feature Comparison

FeatureZustandRedux ToolkitJotaiRecoil
번들 크기~1 KB~30 KB~3 KB~20 KB
보일러플레이트최소중간최소중간
Provider 필요 여부아니오
비동기 처리내장createAsyncThunk내장Selectors
DevTools내장플러그인내장플러그인
학습 곡선약 5 분약 1시간약 15 분약 30 분
TypeScript 지원우수좋음우수좋음

TL;DR

Zustand는 제로 세리머니를 원하는 개발자를 위한 상태 관리 솔루션입니다: 작은 번들, Provider 없음, 리듀서 없음, 그리고 간단하고 타입‑안전한 API를 제공합니다. Redux가 프로젝트에 무겁게 느껴진다면, Zustand는 성능을 희생하지 않으면서 가벼운 대안을 제공합니다.

0 조회
Back to Blog

관련 글

더 보기 »