[React] Zustand 라이브러리를 사용하여 상태 관리하기 (JS/TS)

문지은·2023년 9월 10일
5

React

목록 보기
23/24
post-thumbnail
post-custom-banner

Zustand는 무엇인가?

Zustand는 독일어로 '상태'라는 뜻을 가진 라이브러리이며 Jotai를 만든 카토 다이시가 제작에 참여하고 적극적으로 관리하는 라이브러리이다. 아래의 특징을 가지고 있다.

  • 특정 라이브러리에 엮이지 않는다. (그래도 React와 함께 쓸 수 있는 API는 기본적으로 제공한다.)
  • 한 개의 중앙에 집중된 형식의 스토어 구조를 활용하면서, 상태를 정의하고 사용하는 방법이 단순하다.
  • Context API를 사용할 때와 달리 상태 변경 시 불필요한 리랜더링을 일으키지 않도록 제어하기 쉽다.
  • React에 직접적으로 의존하지 않기 때문에 자주 바뀌는 상태를 직접 제어할 수 있는 방법도 제공한다. (Transient Update라고 한다.)
  • 동작을 이해하기 위해 알아야 하는 코드 양이 아주 적다. 핵심 로직의 코드 줄 수가 약 42줄밖에 되지 않는다. (VanillaJS 기준)

Zustand 사용하기 (JavaScript)

Zustand 설치

  • 프로젝트 루트 위치에서 아래 명령어 실행하여 zustand 설치
# NPM
npm install zustand

# Yarn
yarn add zustand
  • 컴포넌트 구조가 다음과 같을 때, FirstChild 컴포넌트에서 store에 저장된 값을 변경시키고 SecondChild 컴포넌트에서 store에 저장된 값을 출력하도록 해보자.

store 생성

  • 스토어를 생성하기 위해 create 함수를 사용
  • 스토어는 상태 변수와 해당 상태를 업데이트하는 액션(함수)으로 구성
    • 버튼을 선택하는 함수와 count를 증가시키는 함수, count를 리셋하는 함수 작성

store.js

// store.js
import create from "zustand";

const useStore = create((set) => ({
  count: 0,
  selectedButton: null,

  setSelectedButton: (button) => set({ selectedButton: button }),
  incrementCount: () => set((state) => ({ count: state.count + 1 })),
  removeCount: () => set({ count: 0 }),
}));

export default useStore;

상태 변수 및 액션 사용

  • 상태 변수와 액션을 사용하려면 컴포넌트 내에서 useStore 함수를 호출

FirstChild.jsx

// FirstChild.js
import React from "react";
import useStore from "../store/store.js";

function FirstChild() {
  const setSelectedButton = useStore((state) => state.setSelectedButton);
  const incrementCount = useStore((state) => state.incrementCount);
  const removeCount = useStore((state) => state.removeCount);

  const handleClick = (button) => {
    setSelectedButton(button);
  };

  return (
    <div>
      <h1>FirstChild</h1>
      <div>
        <button onClick={() => handleClick("O")}>O</button>
        <button onClick={() => handleClick("X")}>X</button>
      </div>
      <div>
        <button onClick={incrementCount}>카운트 증가</button>
        <button onClick={removeCount}>카운트 리셋</button>
      </div>
    </div>
  );
}

export default FirstChild;
  • store 액션을 아래와 같이 한 줄로 작성할 수도 있음
// FirstChild.js
import React from "react";
import useStore from "../store/store.js";

function FirstChild() {
  const { setSelectedButton, incrementCount, removeCount } = useStore((state) => state);

  const handleClick = (button) => {
    setSelectedButton(button);
  };

  return (
    <div>
      <h1>FirstChild</h1>
      <div>
        <button onClick={() => handleClick("O")}>O</button>
        <button onClick={() => handleClick("X")}>X</button>
      </div>
      <div>
        <button onClick={incrementCount}>카운트 증가</button>
        <button onClick={removeCount}>카운트 리셋</button>
      </div>
    </div>
  );
}

export default FirstChild;

SecondChild.jsx

// SecondChild.js
import React from "react";
import useStore from "../store/store.js";

function SecondChild() {
  const selectedButton = useStore((state) => state.selectedButton);
  const count = useStore((state) => state.count);

  return (
    <div>
      <h1>SecondChild</h1>
      <p>카운트: {count}</p>
      <p>선택한 버튼: {selectedButton}</p>
    </div>
  );
}

export default SecondChild;
  • 마찬가지로 상태변수를 아래와 같이 한줄로 작성할 수도 있음.
// SecondChild.js
import React from "react";
import useStore from "../store/store.js";

function SecondChild() {
	const { count, selectedButton } = useStore((state) => state);

  return (
    <div>
      <h1>SecondChild</h1>
      <p>카운트: {count}</p>
      <p>선택한 버튼: {selectedButton}</p>
    </div>
  );
}

export default SecondChild;

App.jsx

import FirstChild from "./components/FirstChild";
import SecondChild from "./components/SecondChild";

function App() {
  return (
    <div>
      <FirstChild />
      <SecondChild />
    </div>
  );
}

export default App;

실행 결과

Zustand 사용하기 (TypeScript)

  • TypeScript 사용시에는 스토어에 타입 정의가 필요하다.

store.ts

// store.ts
import create from "zustand";

interface Store {
  selectedButton: string | null;
  count: number;
  setSelectedButton: (button: string) => void;
  incrementCount: () => void;
  removeCount: () => void;
}

const useStore = create<Store>((set) => ({
  selectedButton: null,
  count: 0,
  setSelectedButton: (button) => set({ selectedButton: button }),
  incrementCount: () => set((state) => ({ count: state.count + 1 })),
  removeCount: () => set({ count: 0 }),
}));

export default useStore;

FirstChild.jsx

// FirstChild.tsx
import React from "react";
import useStore from "store/store";

function FirstChild() {
  const setSelectedButton = useStore((state) => state.setSelectedButton);
  const incrementCount = useStore((state) => state.incrementCount);
  const removeCount = useStore((state) => state.removeCount);

  const handleClick = (button: string) => {
    setSelectedButton(button);
  };

  return (
    <div>
      <h1>FirstChild</h1>
      <div>
        <button onClick={() => handleClick("O")}>O</button>
        <button onClick={() => handleClick("X")}>X</button>
      </div>
      <div>
        <button onClick={incrementCount}>카운트 증가</button>
        <button onClick={removeCount}>카운트 리셋</button>
      </div>
    </div>
  );
}

export default FirstChild;

SecondChild.jsx

// SecondChild.tsx
import React from "react";
import useStore from "store/store";

function SecondChild() {
  const selectedButton = useStore((state) => state.selectedButton);
  const count = useStore((state) => state.count);

  return (
    <div>
      <h1>SecondChild</h1>
      <p>카운트: {count}</p>
      <p>선택한 버튼: {selectedButton}</p>
    </div>
  );
}

export default SecondChild;

App.tsx

import FirstChild from "components/FirstChild";
import SecondChild from "components/SecondChild";

function App() {
  return (
    <div className="App">
      <FirstChild />
      <SecondChild />
    </div>
  );
}

export default App;

References

profile
코드로 꿈을 펼치는 개발자의 이야기, 노력과 열정이 가득한 곳 🌈
post-custom-banner

1개의 댓글

comment-user-thumbnail
2024년 5월 22일

좋은정보 감사합니다

답글 달기