React의 상태 관리 라이브러리 Zustand를 간단하게 사용해봤다.
쇼핑몰 사이트에서 상품들이 나열될 때 왼쪽에 상품을 필터링할 수 있는 여러 토글 버튼이 나타나는 상황을 가정해 봤다.
Zustand에 만들 store의 명세를 만들어보자.
interface ToggleState {
toggles: Set<string>;
push: (key: string) => void;
pop: (key: string) => void;
toggle: (key: string) => void;
clear: () => void;
}
toggles
가 데이터를 저장하는 객체이다. 각 필터가 고유한 이름을 가지고, 그것을 키라고 한다. toggles
는 활성화된 필터의 키들을 가진 집합이다.
나머지 메서드는 스토어에서 사용할 연산이다. push
는 키를 추가하고, pop
은 키를 제거한다. toggle
은 껐다 켰다에 쓰일 수 있다. 마지막으로 clear
는 모든 키를 집합에서 제거하는 연산이다.
Zustand를 프로젝트에 설치하자.
yarn add zustand
위는 yarn 버전 1을 사용했을 때의 경우이며, npm이나 yarn berry를 쓰고있다면 명령어가 조금 달라질 것이다.
먼저 store 파일을 만든다.
/* src/stores/toggle.ts */
import create from 'zustand';
interface ToggleState {
toggles: Set<string>;
push: (key: string) => void;
pop: (key: string) => void;
toggle: (key: string) => void;
clear: () => void;
}
export const useToggleStore = create<ToggleState>((set) => ({
toggles: new Set(),
push: (key) => set((state) => ({ toggles: new Set(state.toggles).add(key) })),
pop: (key) =>
set((state) => {
const filters = new Set(state.toggles);
filters.delete(key);
return { toggles: filters };
}),
toggle: (key) =>
set((state) => {
const filters = new Set(state.toggles);
if (filters.has(key)) {
filters.delete(key);
} else {
filters.add(key);
}
return { toggles: filters };
}),
clear: () =>
set((state) => {
return { toggles: new Set() };
}),
}));
import create from 'zustand'
로 import한다. create
는 스토어를 만드는 데 사용한다.
아까 보았던 interface가 있고, useToggleStore
라는 객체를 create
했다. 이름에서부터 알 수 있다시피, useToggleStore
는 react hook으로서 사용할 수 있다. 훅의 사용법은 뒤에 나온다.
create
안의 내용은 Zustand repository를 보고 가져왔다. set
를 사용해서 setter를 만들 수 있다.
push
메서드를 읽어보면, 함수 set
에 함수를 인자로 넘긴다. 인자로 넘기는 함수는 기존 상태를 받아 새 상태를 리턴한다. (React의 setState()
를 생각해보면, 이것도 새로운 부분 상태를 리턴하면, 기존 상태와 병합된다는 것을 유추할 수 있다.)
그리고 useToggleStore
를 export하면 프로젝트 어디에서나 사용할 수 있다.
연산 메서드들을 보고 있으니 Redux가 생각난다. Redux와 달리 한 파일의 한 객체에서 작업하니 훨씬 편리하다. Recoil이나 Jotai와 같은 방식과는 다르게 Redux와 비슷한 작성 방식이라고 느껴진다.
스토어를 조작하는 파일을 만들어보자.
/* src/components/Toggles.tsx */
import { useToggleStore } from '../stores/toggle';
function Toggles() {
const onClick: React.MouseEventHandler<HTMLButtonElement> = (e) => {
const { name } = e.target as HTMLButtonElement;
toggle(name);
};
const [toggles, toggle, clear] = useToggleStore((s) => [s.toggles, s.toggle, s.clear]);
console.log({ toggles });
return (
<>
<div>
<button
onClick={onClick}
name="sd"
className={`toggle-button${active('sd', toggles)}`}
>
슈팅배송
</button>
<button
onClick={onClick}
name="ps"
className={`toggle-button${active('ps', toggles)}`}
>
물가안정
</button>
<button
onClick={onClick}
name="pc"
className={`toggle-button${active('pc', toggles)}`}
>
가격비교
</button>
<button
onClick={onClick}
name="fd"
className={`toggle-button${active('fd', toggles)}`}
>
무료배송
</button>
<button onClick={clear} className="toggle-button">
초기화
</button>
</div>
</>
);
}
export default Toggles;
function active(key: string, toggles: Set<string>): string {
return toggles.has(key) ? ' active' : '';
}
중요한 곳은 useToggleStore
를 호출하는 곳이다.
const [toggles, toggle, clear] = useToggleStore((s) => [s.toggles, s.toggle, s.clear]);
왼쪽에서는 hook을 사용하는 것 처럼 호출하면 된다. 객체의 구조 분해 할당을 사용해도 괜찮다. useToggleStore
에 함수를 인자로 넘겨서 사용하고 싶은 속성만 가져오면 된다. 함수를 넘기지 않으면 스토어 전체를 가져온다.
다만 이 때 함수를 넘기지 않으면 성능에 안좋을 수 있다. 위의 코드처럼 간단하면 상관없겠지만, 그게 아니라면 성능을 고려해 보자.
훅을 사용하며 인자로 함수를 넘기는 것이 조금 귀찮은 것 같았다. 다른 라이브러리와 비교해 볼까? Redux를 훅으로 사용할 때, 나는 각 redux 스토어를 "모듈"로 만들어, 스토어를 사용하는 훅을 만들어서 그걸 모듈 쪽에서 export했었다. 말로 설명하니 잘 안 와닿지만, 아무튼 매 스토어마다 스토어를 사용하는 get/set 등의 훅을 만들어서 export 하고, 바깥에서는 그 훅을 import 해서 사용했다. 매 스토어마다 훅을 제작하던 것에 비하면 지금이 간편해졌다.
스토어를 조작하지 않고 값만 사용하려면 다음처럼 만들 수 있을 것이다.
/* src/components/Content.tsx */
import { useToggleStore } from '../stores/toggle';
function Content() {
const filters = useToggleStore((s) => s.filters);
return <></>;
}
export default Content;
예를 들면, filters를 사용해서 query를 전송할 수 있겠다.
간단하게 Zustand를 써 봤는데 확실히 Redux보다 간편하면서도, Redux를 대체하여 사용 가능해 보인다.
객체 깊이가 2단계 이상인 상태를 다루려면 Immer를 사용하라고 권장하고 있다. Immer는 Redux 스토어의 reducer를 작성할 때에도 써본 적이 있다. (JS로도 작성할 수 있지만, 계속해서 ...
를 반복하다 보면 실수를 할 수도 있는데, 그걸 방지하기에 좋다.)
내가 쓴 글보다 Zustand를 이해하는 데 훨씬 좋은 글이다. React 상태 관리 라이브러리 Zustand의 코드를 파헤쳐보자 | TOAST UI :: Make Your Web Delicious!