BioPlayground

🧬
목록으로

Zustand — 리액트 상태 관리 2분 컷

Zustand가 Redux보다 간결한 이유, 사용법, 그리고 어떤 규모에 적합한지를 설명합니다.

중급
|
5
|
검증 완료 (2026-07)
진행률0/36 (0%)

Zustand — 리액트 상태 관리 2분 컷

이 토픽을 마치면

Zustand의 기본 사용법을 이해하고, Redux와의 차이를 설명할 수 있게 됩니다.


React의 고질적인 문제: props 전달 지옥

React에서 데이터를 전달하려면 props를 씁니다. 부모 → 자식 → 손자 → 증손자... 데이터가 필요한 컴포넌트까지 계속 내려보내야 합니다.

text
App → Header → UserMenu → UserAvatar
                              ↑ 여기서 user 데이터가 필요

user 데이터가 App에 있고 UserAvatar에서 필요하면, 중간의 HeaderUserMenu는 쓰지도 않는 user를 받아서 전달만 해야 합니다. 이걸 Prop Drilling이라고 합니다.

컴포넌트가 50개, 100개가 되면 이게 관리 불가능해집니다. 그래서 전역 상태 관리 도구가 등장했습니다.


Redux의 시대와 그 피로감

Redux는 React 전역 상태 관리의 원조입니다. 강력하지만 코드가 많습니다:

javascript
// 1. 액션 타입 정의
const INCREMENT = "INCREMENT";

// 2. 액션 생성 함수
const increment = () => ({ type: INCREMENT });

// 3. 리듀서
const counterReducer = (state = { count: 0 }, action) => {
  switch (action.type) {
    case INCREMENT:
      return { ...state, count: state.count + 1 };
    default:
      return state;
  }
};

// 4. 스토어 생성
const store = createStore(counterReducer);

// 5. Provider로 앱 감싸기
// 6. useSelector로 읽기
// 7. useDispatch로 쓰기

숫자 하나 올리는데 파일 여러 개, 코드 수십 줄입니다. 이게 Redux의 보일러플레이트 문제입니다. Redux Toolkit이 나와서 많이 줄었지만, 여전히 "이게 이렇게 복잡할 일인가?" 싶은 경우가 있습니다.


Zustand: 같은 일을 5줄로

javascript
import { create } from "zustand";

const useStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
}));

끝입니다. 스토어 정의가 5줄입니다. Provider로 앱을 감쌀 필요도 없습니다.

컴포넌트에서 쓸 때:

javascript
function Counter() {
  const count = useStore((state) => state.count);
  const increment = useStore((state) => state.increment);

  return <button onClick={increment}>{count}</button>;
}

useStore를 호출하면 끝입니다. 어떤 컴포넌트에서든, 트리 어디에서든, 그냥 useStore를 부르면 전역 상태에 접근할 수 있습니다.


왜 이렇게 간단한가

Zustand는 React 외부에 독립적인 스토어를 만듭니다. React의 Context API나 Provider에 의존하지 않습니다. 그래서:

Provider 불필요<Provider store={store}> 같은 래퍼가 없습니다. 어디서든 useStore만 호출하면 됩니다.

보일러플레이트 최소 — 액션 타입, 액션 생성 함수, 리듀서 분리가 없습니다. set 함수 하나로 상태를 변경합니다.

선택적 렌더링useStore((state) => state.count)처럼 필요한 값만 선택합니다. count가 바뀔 때만 이 컴포넌트가 리렌더링됩니다. 불필요한 리렌더링이 자동으로 방지됩니다.


실무 패턴: 비동기 + 미들웨어

API 호출 같은 비동기 작업도 자연스럽게 처리됩니다:

javascript
const useStore = create((set) => ({
  users: [],
  loading: false,
  fetchUsers: async () => {
    set({ loading: true });
    const res = await fetch("/api/users");
    const users = await res.json();
    set({ users, loading: false });
  },
}));

persist 미들웨어를 쓰면 상태를 localStorage에 자동 저장합니다:

javascript
import { persist } from "zustand/middleware";

const useStore = create(
  persist(
    (set) => ({
      theme: "light",
      toggleTheme: () =>
        set((s) => ({ theme: s.theme === "light" ? "dark" : "light" })),
    }),
    { name: "app-settings" }
  )
);

페이지를 새로고침해도 테마 설정이 유지됩니다.


Redux를 버려야 하나

아닙니다. 규모에 따라 다릅니다.

Zustand가 적합한 경우 — 중소규모 프로젝트, 빠른 프로토타이핑, 전역 상태가 단순한 경우. 설정 시간이 거의 없어서 바로 시작할 수 있습니다.

Redux가 적합한 경우 — 대규모 팀 프로젝트, 상태 변화 추적이 중요한 경우(Redux DevTools의 타임 트래블 디버깅), 엄격한 패턴이 팀 컨벤션으로 필요한 경우.

npm 다운로드 수 기준으로 Zustand는 이미 Redux에 근접하거나 추월하고 있습니다. "작고 가벼운 것이 좋다"는 트렌드와 맞아떨어지기 때문입니다.


핵심

Zustand는 Provider 없이, 보일러플레이트 없이 React 전역 상태를 관리하는 라이브러리입니다. create 함수로 스토어를 만들고, useStore 훅으로 어디서든 접근합니다. 중소규모 프로젝트에서 Redux의 현실적인 대안입니다.