[React] Zustand 간단 사용기

telnet turtle·2022년 10월 8일
0

Zustand

목록 보기
2/2

React의 상태 관리 라이브러리 Zustand를 간단하게 사용해봤다.

쇼핑몰에서 사용할 간단한 토글 필터

쇼핑몰 사이트에서 상품들이 나열될 때 왼쪽에 상품을 필터링할 수 있는 여러 토글 버튼이 나타나는 상황을 가정해 봤다.

store 명세 작성

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 설치

Zustand를 프로젝트에 설치하자.

yarn add zustand

위는 yarn 버전 1을 사용했을 때의 경우이며, npm이나 yarn berry를 쓰고있다면 명령어가 조금 달라질 것이다.

store 파일 제작

먼저 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와 비슷한 작성 방식이라고 느껴진다.

store 사용

스토어를 조작하는 파일을 만들어보자.

/* 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 해서 사용했다. 매 스토어마다 훅을 제작하던 것에 비하면 지금이 간편해졌다.

또 다른 store 사용

스토어를 조작하지 않고 값만 사용하려면 다음처럼 만들 수 있을 것이다.

/* 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!

profile
프론트엔드 엔지니어

0개의 댓글