본문 바로가기

SSAFY/SSAFYcial

리액트 상태 관리 라이브러리 Zustand

 
안녕하세요, 오늘은 리액트의 상태 관리 라이브러리 중 하나인 Zustand에 대해 알아보고 기초적인 코드를 통해 이해해보도록 하겠습니다.
 

1. Zustand는 무엇인가?

Zustand는 독일어로 '상태'라는 뜻을 의미합니다. Zustand는 리액트 상태 관리를 위한 라이브러리로서 다음과 같은 특징을 가지고 있습니다.
 

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

 
Zustand에 대해 본격적인 이해에 앞서 리액트(React)의 컴포넌트 특징에 대해 이해해야 합니다.
 
리액트에서 변수를 사용할 때 컴포넌트 내의 변수(state)를 공유하기 어렵다는 단점이 존재합니다. (props, context api를 사용하면 변수를 공유할 수 있습니다. 그러나 props나 context api 같은 경우 변수 값이 한 번 변경될 때마다 영향을 받는 모든 자식 컴포넌트가 리렌더링 되므로 애플리케이션 성능 저하를 일으키는 문제가 발생합니다)
 

리액트에서 변수를 관리할 때 리덕스(Redux)를 사용해서 변수 값을 관리할 수 있는데요, 리덕스는 문법이 어려워서 러닝 커브가 높은 편입니다. 그래서 신규 사이트나 서비스들은 다른 상태 관리 라이브러리를 많이 사용하는데 오늘은 그 중에서 Zustand로 상태를 관리하는 방법을 알아보겠습니다.
 


2. Zustand 사용하기(Javascript)

(1) Zustand 설치하기

프로젝트 루트 위치에서 아래 명령어를 실행하여 zustand를 설치합니다.
 

# NPM
npm install zustand

# Yarn
yarn add zustand

 

(2) 실습 목표: store에 저장된 값을 변경시켜서 다른 컴포넌트에서 변경된 값 확인

App.jsx
   │--FirstChild
   ┗--SecondChild

 
컴포넌트 구조가 위와 같을 때, FirstChild 컴포넌트에서 store에 저장된 값을 변경시켰을 때 SecondChild 컴포넌트에서 store에 저장된 값을 출력해서 잘 변경되는지 실습을 통해 이해해봅시다.
 

(3) store 생성

스토어를 생성하기 위해 가장 윗줄에 zustand에서 create 함수를 불러와 줍니다.
 

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

 
스토어는 상태 변수와 해당 상태를 업데이트하는 액션(함수)로 구성되어 있습니다. 버튼을 선택하는 함수와 count를 증가시키는 함수, count를 리셋하는 함수를 만들어 보겠습니다. (아래 코드 참고)
 

// 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 })),  // count를 1씩 더해주는 함수
  removeCount: () => set({ count: 0 }),                                // count를 리셋하는 함수
}));

export default useStore;

 

(4) 상태 변수 및 액션 사용

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

// FirstChild.jsx
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.jsx
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;

 
이렇게 FirstChild.jsx와 SecondChild.jsx를 App.jsx에서 호출한 뒤에 코드를 실행시켜 보겠습니다.
 

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

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

export default App;

 


3. Zustand 사용하기(Typescript)

Typescript는 위 코드에서 타입 선언을 해주는 것 외에는 다른 것이 없습니다.
 

// 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.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.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;

 

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

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

export default App;

 
 

참고한 문서

1. zustand documentation: https://docs.pmnd.rs/zustand/getting-started/introduction

Zustand Documentation

Zustand is a small, fast and scalable bearbones state-management solution, it has a comfy api based on hooks

docs.pmnd.rs

2. zustand github: https://github.com/pmndrs/zustand#transient-updates-for-often-occuring-state-changes

GitHub - pmndrs/zustand: 🐻 Bear necessities for state management in React

🐻 Bear necessities for state management in React. Contribute to pmndrs/zustand development by creating an account on GitHub.

github.com

 
이번에는 리액트 상태 관리 라이브러리인 Zustand에 대해 알아보았습니다.