zustand는 상태 관리 라이브러리이다. 사용이 간편하며 보일러플레이트 코드 또한 아주 적다. context api를 이용하여 상태 관리를 할 때 발생하는 리렌더링은 context 분리, memoization 등을 통해 해결해야 한다. 반면 zustand를 이용하면 상태 변경 시 불필요한 리렌더링을 쉽게 방지할 수 있고 provider hell 문제 또한 해결할 수 있다.

기본적인 사용법

기본적인 사용법은 먼저 create 함수를 이용하여 스토어를 생성하는 것이다.

import { create } from "zustand";

interface CountState {
  counts: number;
  increaseCount: () => void;
}

const useCountStore = create<CountState>(set => ({
  counts: 0,
  increaseCount: () => set(state => ({ counts: state.counts + 1 })),
}));

그리고 컴포넌트에서 selector 함수를 전달하여 훅을 사용하면 된다.

function Counter() {
  const count = useCountStore(state => state.count);

  return <div>{count}</div>;
}

function IncreaseButton() {
  const increaseCount = useCountStore(state => state.increaseCount);

  return <button onClick={increaseCount}>증가</button>;
}

immer도 같이 사용 가능하며 devtools, persist 등의 여러가지 유용한 기능들이 있다.

zustand 동작 원리 이해하기

이제 zustand 코드를 통해 어떻게 동작하는지 알아보자. zustand는 발행/구독 모델 기반으로 이루어져 있으며, 내부적으로 스토어 상태를 클로저로 관리한다. 아래는 type과 deprecated 부분을 제외한 vanilla 코드인데, 상태 변경을 구독할 리스너는 Set을 통해 관리하고 있다.

// vanilla.ts
const createStoreImpl = createState => {
  let state;
  const listeners = new Set();

  const setState = (partial, replace) => {
    // ... (생략)
  };

  const getState = () => state;

  const subscribe = listener => {
    // ... (생략)
  };

  const api = { setState, getState, subscribe };
  state = createState(setState, getState, api);

  return api;
};

export const createStore = createState =>
  createState ? createStoreImpl(createState) : createStoreImpl;

ref: https://ingg.dev/zustand-work/#zustand-intro

ref: https://jforj.tistory.com/341