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는 발행/구독
모델 기반으로 이루어져 있으며, 내부적으로 스토어 상태를 클로저
로 관리한다. 아래는 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;