Home Zustand
Post
X

Zustand

Zustand에 대해 알아보자.


Zustand

Zustand는 redux에 비해 간단하고 직관적인 상태 관리(Store) 라이브러리입니다.

  • 심플한 API : Redux처럼 복잡한 액션/리듀서 없음

  • 가벼움 : minified + gzip 기준 약 1KB

  • 서브스크립션 최적화 : 필요한 상태만 업데이트됨

  • 미들웨어 지원 : persist, devtools, immer 등 확장 가능


Store

Store는 여러 상태(State)를 중앙에서 관리하는 패턴을 말합니다.

컴포넌트 간 공유할 데이터를 중앙에서 관리하여 전달하는 중간 단계 컴포넌트가 필요치 않으므로, 컴포넌트 간 결합도를 낮추고 유지/보수를 쉽게 만듭니다.

zustand-store


차별점

  • Redux

    Redux에 비해서 간결하고 Provider 필요 없이 상태 변경이 가능합니다.

  • Valtio

    Valtio는 가변 상태 모델을 기반으로 하고 Propertiy aceess를 통해서 랜더링 최적화를 합니다.

    Zustand는 불변 상태 모델을 기반, 렌더링 최적화에 selector 사용

  • Jotai & Recoil

    Jotai는 atom 기반으로 해서 복잡한 상태 관리 로직을 구성할 수 있습니다.

    Zustand는 single store 기반


create 함수

Store를 생성하는 가장 기본적인 함수입니다.

create() 함수 내부에서 set, get을 사용해서 상태와 액션(함수)들을 정의합니다.

  • set : 상태를 업데이트하는 함수
  • get : 현재 상태를 읽는 함수
1
2
3
4
5
6
7
import { create } from "zustand";

const useBearStore = create((set) => ({
  bears: 0,
  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
  removeAllBears: () => set({ bears: 0 }),
}));

Store 생성하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// store/useCounterStore.ts
import { create } from "zustand";

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

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

Hook으로 사용하는 방식

React 컴포넌트 내에서만 사용 가능한 방식으로 상태 변경 시 자동으로 리렌더링됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// components/Counter.tsx
import { useCounterStore } from "@/store/useCounterStore";

export default function Counter() {
  const { count, increment, decrement, reset } = useCounterStore();

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={increment}>증가</button>
      <button onClick={decrement}>감소</button>
      <button onClick={reset}>리셋</button>
    </div>
  );
}
  • 일부만 사용하기

    1
    2
    
    const count = useCounterStore((state) => state.count);
    const increment = useCounterStore((state) => state.increment);
    

Hook 없이 사용해보기

Zustand는 내부적으로 create() 함수를 통해 store 객체를 반환합니다.

객체에는 메서드가 포함되어 있어 Hook 없이도 사용할 수 있습니다.

React 컴포넌트 외부에서도 자유롭게 사용 가능합니다.

상태 변경 감지 및 자동 리렌더링 없음

  • getState() : 현재 상태를 반환

  • setState(partialState) : 상태를 직접 설정

  • subscribe(listener) : 상태 변화가 일어날 때 콜백 실행

  • destroy() : 모든 Store 삭제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { useUserStore } from "@/stores/userStore";

// 현재 상태 가져오기
const currentUser = useUserStore.getState().user;

// 상태 직접 설정
useUserStore.setState({ user: { id: "1", name: "길동", email: "" } });

// subscribe 사용
const unsub = useUserStore.subscribe((state) => {
  console.log("변경된 유저:", state.user);
});

// Store 삭제
useUserStore.destroy();

상태 분할

Zustand는 각 Store가 독립된 상태 저장소입니다.

즉, Store가 변경되어도 다른 Store를 사용하는 컴포넌트는 리렌더되지 않습니다.

목적에 따라 모듈을 나누면 유지보수성과 가독성이 좋아집니다.

1
2
3
4
5
6
7
8
9
10
11
12
// useUserStore.ts
const useUserStore = create((set) => ({
  user: null,
  login: (name) => set({ user: { name } }),
  logout: () => set({ user: null }),
}));

// useThemeStore.ts
const useThemeStore = create((set) => ({
  darkMode: false,
  toggle: () => set((s) => ({ darkMode: !s.darkMode })),
}));

Redux DevTools

Redux DevTools Extension은 브라우저 개발자 도구에서 상태 변화의 이력을 시각적으로 추적할 수 있게 해주는 확장입니다.

이 확장은 Zustand에서도 사용할 수 있습니다.

  • 상태 변경 이력 추적

    • 상태가 어떻게 바뀌었는지 순서대로 나열
    • 이전 상태와 다음 상태를 비교
    • 어떤 액션이 상태를 바꿨는지 확인 가능
  • 시간 되감기

    • 상태 변경을 되돌릴 수 있음
    • 특정 시점으로 “롤백”할 수 있음
  • 상태 수동 조작 가능

    • DevTools에서 값을 직접 입력해서 상태 변경 가능

Zustand는 devtools 미들웨어를 통해 Redux DevTools와 연동됩니다.

상태가 크거나 민감한 경우 DevTools 연동은 성능 이슈나 보안상 주의 필요

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// store/useCounterStore.ts
import { create } from "zustand";
import { devtools } from "zustand/middleware";

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

export const useCounterStore = create<CounterState>()(
  devtools(
    (set) => ({
      count: 0,
      increment: () =>
        set((state) => ({ count: state.count + 1 }), false, "increment"),
      decrement: () =>
        set((state) => ({ count: state.count - 1 }), false, "decrement"),
    }),
    { name: "CounterStore" } // DevTools에서 store 이름으로 표시됨
  )
);
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.