Skip to content

Zustand 상태 설계 패턴

Zustand는 자유로운 상태 관리 도구이지만, 어떤 구조로 설계하냐에 따라 유지 보수성과 확장성이 크게 달라진다.

💡 Zustand 상태 설계의 핵심

  1. 스토어 구조화: 상태, 액션, 계산된 값 구분
  2. 선택적 구독: selector로 필요한 상태만 선택
  3. 안전한 업데이트: set/get로 불변성 유지
  4. 복잡한 구조 관리: 중첩 객체·배열도 불변성 지키기

스토어 설계 구조

Zustand 스토어는 크게 상태(State), 액션(Action), 계산된 값(Computed Value) 으로 나눌 수 있다.

jsx
const useAppStore = create((set, get) => ({
  // 상태
  count: 0,

  // 액션
  increment: () => set((state) => ({ count: state.count + 1 })),

  // 계산된 값
  doubleCount: () => get().count * 2,
}));
  • set(): 상태 변경
  • get(): 현재 상태 조회

역헐울 구분해 두면 가독성과 유지보수성이 좋아진다.


상태 선택 (Selector)

Zustand는 필요한 값만 선택적 구독할 수 있다. 이 방식은 불필요한 리렌더링을 줄여 성능 최적화에 도움이 된다.

jsx
const name = useUserStore((state) => state.user.name);
const isLoggedIn = useUserStore((state) => state.user.isLoggedIn);
  • name만 바뀌면 isLoggedIn을 사용하는 컴포넌트는 리렌더링되지 않는다.
  • (state) => state.user.name; 같은 함수를 selector 함수라고 부른다.

💡 최적화 Tip

  • 꼭 필요한 값만 선택
  • selector 함수는 컴포넌트 바깥에서 선언해 재사용
  • 객체 비교 최적화를 위해 useShallow 훅 활용

상태 업데이트 (set, get)

Zustand는 불변성을 지키며 상태를 변경해야 한다.

jsx
setCount: (value) => set({ count: value });
increment: () => set((state) => ({ count: state.count + 1 }));
doubleCount: () => get().count * 2;

불변성이 중요한 이유

💡 불변성이란?

한 번 생성된 값은 변경되지 않는다는 성질을 의미한다.
상태는 직접 변경하면 변경 사실을 추적하기 어렵고, 예기치 않은 부작용이 발생할 수 있다.

  • React는 === 얕은 비교로 상태 변경 여부를 판단한다.
  • 객체/배열을 직접 수정하면 변경을 감지하지 못해 리렌더링이 안될 수 있다.
  • 따라서 상태를 바꿀 때는 매번 새로운 객체/배열을 생성해야 한다.

복잡한 상태 구조 다루기

중첩 객체 업데이트

jsx
updatePersonalInfo: (newInfo) =>
  set((state) => ({
    profile: {
      ...state.profile,
      personal: {
        ...state.profile.personal,
        ...newInfo,
      },
    },
  }));
  • 기존의 profile, personal을 펼친 뒤, 새로운 값 newInfo만 덮어쓴다.
  • 나머지 값은 유지되면서 원하는 값만 안전하게 업데이트 된다.
  • 복잡한 중첩 상태는 Immer 라이브러리 고려

배열 업데이트

jsx
addTask: (task) =>
  set((state) => ({
    tasks: [...state.tasks, task],
  }));
  • 기본 배열을 복사 (...state.tasks) 후 새 값을 추가해야 React가 변경을 감지하고 리렌더링이 발생한다.