All'alba vincerò

At dawn, I will win!

React

[React] Zustand 라이브러리: 상태(state) 관리 라이브러리

나디아 Nadia 2024. 8. 25. 22:02

📌 Zustand

: React에서 상태를 관리하기 위한 경량의 상태 관리 라이브러리

  • 상태 스토어(store)를 쉽게 만들 수 있다.
    • 상태 스토어(store): 플리케이션의 상태를 저장하고 관리하는 중앙 저장소
    • 상태 스토어를 사용하면 애플리케이션의 여러 컴포넌트에서 동일한 상태를 공유하고 관리할 수 있다.
    • 글로벌 상태를 내부에서 관리할 수 있다.
  • 장점
    • 독선적이지 않고, 특정 작업 방식을 강요하지 않는다.
    • 사전에 작성해야 할 예열(boilerplate) 코드가 적다.
    • 컨텍스트 프로바이더(provider)에 의존하지 않는다.
    • 컨텍스트(context)를 사용하는 것보다 더 빠르다.
    • 기본적으로 상태를 병합(merge)해 구문 작성이 편리하다.
    • 미들웨어(middleware)를 사용해 확장(extendable)할 수 있다.

 

 

✨ RTK Query  vs  Zustand

  RTK Query Zustand
목적 서버 데이터 페칭 및 캐싱 관리 전역 상태 관리
기반 기술 Redux Toolkit 독립적인 상태 관리 라이브러리
주요 기능 비동기 데이터 페칭, 캐싱, 요청 상태 관리 전역 상태 관리, 간단한 상태 업데이트
복잡성 1) 상대적으로 복잡
2) 더 많은 기능 제공
1) 간단하고 가벼움
2) 사용이 쉬움
사용 방법 Redux 스토어와 통합, 페칭 및 상태 자동 관리 훅을 사용한 상태 설정 및 업데이트
상태 관리 대상 서버에서 가져온 데이터의 상태 클라이언트 측 전역 상태
미들웨어 지원 Redux 미들웨어 사용 로깅, 비동기 처리, 퍼시스턴스 지원
사용 상황 API 요청이 빈번하고 복잡한 상태 관리가 필요 간단한 상태 관리가 필요한 경우

 

 


 

⏩ 설치

  • Zustand 패키지 설치
npm i zustand

// or

pnpm add zustand

 

 

 

✅ 사용

1. 상태 스토어(store) 생성

  • create() 사용
// store.js
import create from 'zustand';

// 상태 스토어 생성
const useStore = create((set) => ({
  count: 0, // 초기 상태
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
}));

export default useStore;

 

 

 

2. 상태 사용

  • useStore() 사용
// Counter.js
import React from 'react';
import useStore from './store';

const Counter = () => {
  // 상태와 메서드를 훅으로 가져오기
  const { count, increment, decrement } = useStore((state) => ({
    count: state.count,
    increment: state.increment,
    decrement: state.decrement,
  }));

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  );
};

export default Counter;

 

 


✅ 기능

 

☑️ create()

: 상태 스토어(Store)를 관리하는 커스텀 훅 함수를 생성

  • 상태(state)와 그 상태를 업데이트하는 함수(setState)를 정의
    ➡︎ 이것을 사용할 수 있는 커스텀 훅(hook)을 반환한다.
import create from 'zustand';

const useStore = create((set, get, store) => ({
  // 초기 상태 및 상태를 업데이트하는 함수들 정의
}));
import create from 'zustand';

const useStore = create((set) => ({
  bears: 0,
  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
  removeAllBears: () => set({ bears: 0 }),
}));
// useStore라는 훅(hook)을 생성
// - 이 훅을 컴포넌트에서 호출하여 상태(bears)와 상태를 변경하는 함수(increasePopulation, removeAllBears)를 사용할 수 있다.

 

 

👉 매개변수로 전달되는 함수의 인수

  • set
    : 상태를 업데이트하는 함수
    • 상태를 새 객체로 교체하거나 기존 상태를 기반으로 업데이트하는 데 사용
    • 이 함수를 사용해 상태를 동적으로 변경할 수 있다.
set(상태 업데이트 로직)
const useStore = create((set) => ({
  bears: 0,
  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
}));

 

 

  • get
    : 현재 상태를 가져오는 함수
    • 상태를 참조하거나 조건에 따라 상태를 업데이트할 수 있다.
get.키
const useStore = create((set, get) => ({
  bears: 0,
  increasePopulationIfFew: () => {
    if (get().bears < 10) {
      set((state) => ({ bears: state.bears + 1 }));
    }
  },
}));

 

 

  • store
    : 스토어(store) 객체 자체를 참조
    • store를 통해 상태와 set 및 get 같은 함수들을 포함한 전체 스토어를 직접 참조할 수 있다.
    • 주로 특수한 경우나 커스텀 로직이 필요한 상황에서 사용된다.
store.메서드
const useStore = create((set, get, store) => ({
  bears: 0,
  logStore: () => {
    console.log(store.getState());  // 스토어의 전체 상태를 출력
  },
}));

 

 

 

예제 1) 카운터 증가

export const useCounter = create((set /*, get */) => ({
  count: 0,
  increment: (by) => set((s) => ({ count: s.count + by })),
}));


// 풀어쓴 형태 ----------
export const useCounter = create((set /*, get */) => ({
  increment: (by) => {
    const prevCount = get().count;
    set(() => {
      return {
        count: prevCount + by,
      };
    });
  },
}));

 

 

예제 2) set, get, store 모두 활용

const useStore = create((set, get, store) => ({
  bears: 0,
  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
  resetPopulation: () => set({ bears: 0 }),
  logCurrentState: () => {
    const currentState = get();
    console.log(currentState.bears);
  },
  logStore: () => {
    console.log(store.getState());
  },
}));

 

 

 

 

☑️ createStore()

: 상태 스토어(Store)를 생성하지만, 상태 스토어(Store) 객체 자체를 반환

  • 커스텀 훅 대신, 스토어 객체를 사용하여 상태를 공유한다.
  • 객체를 직접 다루면서 상태 관리 로직을 조금 더 커스터마이즈할 수 있다.
  • 바닐라 JS 프로젝트에서 사용
  • 장점(유용)
    • 컨텍스트나 컴포넌트 속성(props)을 통해 스토어를 전달하고 싶을 때
    • 상태(state)를 직접 다루는 경우
    • Zustand 외부에서 상태를 관리해야 할 때 
import { createStore } from 'zustand';

const store = createStore((set) => ({
  bears: 0,
  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
  removeAllBears: () => set({ bears: 0 }),
}));


// 상태를 직접 접근하거나 변경할 수 있다.
store.setState({ bears: 5 });
console.log(store.getState().bears); // 5

 

 

 

 

☑️ useStore()

: 생성된 상태 스토어(store)를 컴포넌트에서 사용할 수 있는 훅

  • 이 훅을 통해 상태(state)와 상태 업데이트 함수(setState)를 구독하고 사용할 수 있다.
// store 불러오기
import useStore from './store';

// 단일 상태 접근 
const count = useStore((state) => state.count);

// ------------------------------------------

// 여러 상태 접근 
const { count, increment, reset } = useStore((state) => ({
    count: state.count,
    increment: state.increment,
    reset: state.reset
  }));
function BearCounter() {
  // bears 상태를 구독하고 그 값을 화면에 출력한다.
  const bears = useStore((state) => state.bears);

  return <h1>{bears} bears around here ...</h1>;
} 


function Controls() {
  // increasePopulation 함수를 호출하여 bears 상태를 1 증가시킨다.
  const increasePopulation = useStore((state) => state.increasePopulation);
  
  return <button onClick={increasePopulation}>Increase population</button>;
}

 

 

 

✨ 상태 선택(Selectors)

: 특정 상태만 구독할 수 있는 선택기(Selector) 기능

  • 성능이 향상되고 불필요한 리렌더링을 줄일 수 있다.
  • useStore 훅의 인자로 함수를 전달하여 상태의 일부를 선택한다.
function BearCounter() {
  // 상태의 bears만 구독
  const bears = useStore((state) => state.bears);
  
  return <h1>{bears} bears around here ...</h1>;
}


function Controls() {
  // 상태의 increasePopulation 함수만 구독
  const increasePopulation = useStore((state) => state.increasePopulation);
  
  return <button onClick={increasePopulation}>Increase population</button>;
}

 


 

☑️ set()

: 상태를 업데이트 하는 함수

    • 현재 상태와 함께 상태 업데이트 함수가 호출되어 새로운 상태를 반환한다.
    • 전역 상태를 쉽게 관리하고, React 컴포넌트에서 그 상태를 기반으로 UI를 업데이트할 수 있다.

 

👉 상태 업데이트 방식

  • 함수(Function) 형태로 상태 업데이트
    • 이전 상태를 인수로 받아 새로운 상태를 반환하는 함수로 상태를 설정
    • 상태를 동적으로 업데이트 한다.
      increasePopulation: () => set((state) => ({ bears: state.bears + 1 }))
      // 현재 상태가 { bears: 0 }이고 set((state) => ({ bears: state.bears + 1 }))를 호출하면, 
      // 상태는 { bears: 1 }로 업데이트 된다.
  • 객체(Object) 형태로 상태 업데이트
    • 상태를 새로운 객체로 직접 설정
    • 상태를 정적으로 업데이트 한다.
      resetPopulation: () => set({ bears: 0 })
      // 현재 상태가 { bears: 0 }이고 set({ bears: 5 })를 호출하면, 상태는 { bears: 5 }로 업데이트 된다.
import create from 'zustand';

const useStore = create((set) => ({
  bears: 0,
  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
  resetPopulation: () => set({ bears: 0 }),
}));

 

 

 

 

☑️ getState()

: 상태(state)를 직접 가져올 수 있는 메서드

const currentState = 상태 스토어(store).getState();
  • 상태 스토어(store)에서 현재 저장된 상태를 반환한다.
  • 비동기 작업이나 컴포넌트 외부에서 상태(state)를 읽어올 때 사용한다.
    ex) 상태를 기반으로 API 호출을 하거나 특정 조건을 만족할 때 상태를 확인할 수 있다.
import create from 'zustand';

// 상태 스토어 생성
const useStore = create((set, get) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  getCurrentCount: () => get().count, // 상태를 반환하는 메서드
}));

// 컴포넌트 외부에서 상태 가져오기
const currentState = useStore.getState();
console.log(currentState.count); // 현재 count 값을 출력

 

 

예제) 비동기 작업

const useStore = create((set, get) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
}));

// 비동기 함수
async function fetchData() {

  // 현재 상태를 가져옴
  const currentState = useStore.getState();
  
  // 상태 기반으로 조건 확인 후 API 호출
  if (currentState.count > 5) {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    console.log(data);
    
  } else {
    console.log('Count is too low.');
  }
}

 

 

 

 

☑️ store.getInitialState()

: 생성된 상태 스토어(store)에서 초기 상태를 가져오는 메서드

  • 이 메서드는 상태를 리셋할 때 사용된다.
  • Zustand의 최신 버전에서는 공식적으로 제공되지 않는다.
    ➡︎ 상태의 초기값은 create() 함수의 인자로 설정하는 방식으로 정의다.
import create from 'zustand';

// 초기 상태 정의
const initialState = {
  count: 0,
};

// 상태 스토어 생성
const useStore = create((set) => ({
  ...initialState, // 초기 상태 설정
  increment: () => set((state) => ({ count: state.count + 1 })),
}));

// 초기 상태 접근
const getInitialState = () => ({ ...initialState });
console.log(getInitialState().count); // 0

 

 

예제) 카운터 로직

// - [x] 관리할 상태 : count, step, min, max, increment, decrement, reset
// - [x] 파생된 상태 : isMinDisabled, isMaxDisabled

import { create } from 'zustand';

// 상태 저장소를 생성
export const useCountStore = create((set, get, store) => {
  // 증가 함수
  const increment = () => {
    // set = 상태를 업데이트
    set(({ count, step, max }) => {
      // 다음 값 = 현재값 + 증감값
      let nextCount = count + step;

      // 최댓값 제한 (다음 값이 최댓값에서 멈춤)
      if (nextCount >= max) nextCount = max;

      return { count: nextCount };
    });
  };

  // 감소 함수
  const decrement = () =>
    set(({ count, step, min }) => {
      // 다음 값 = 현재값 - 증감값
      let nextCount = count - step;

      // 최솟값 제한 (다음 값이 최솟값에서 멈춤)
      if (nextCount <= min) nextCount = min;

      return { count: nextCount };
    });

  // 리셋 함수
  // - store.getInitialState() = 초기 상태를 반환하는 메서드
  const reset = () => set(store.getInitialState());

  // 상태 값 반환
  return {
    count: 0,
    step: 1,
    min: 0,
    max: 10,
    increment,
    decrement,
    reset,
  };
});

 

 

 

 

☑️ devtools()

: 개발자 도구와 연동하여 상태 변경을 추적하고 디버깅할 수 있는 기능

  • Zustand 스토어(store)를 디버깅하고 상태 변화를 쉽게 추적할 수 있도록 도와주는 도구
  • Zustand에서 제공하는 미들웨어 중 하나
  • 특징
    • 상태 변경 추적
      : 애플리케이션에서 상태가 변경될 때마다 그 변화를 추적할 수 있다.
      ex) 특정 액션(ex. 'addTask', 'deleteTask')이 호출되었을 때, 상태가 어떻게 변화했는지를 시각적으로 확인할 수 있다.
    • 디버깅 용이
      : 개발 중에 상태 변경의 흐름을 쉽게 파악할 수 있어, 디버깅이 더 편리해진다.
    • 시간 여행 디버깅
      : 상태 변경 전후의 상태를 비교하거나, 이전 상태로 되돌아가서 다시 살펴볼 수 있는 기능이 있다.

 

⏩ 사용 

  1. import로 devtools() 불러오기
  2. devtools()로 store를 감싸고, create 함수에 전달하기
    ➡︎ Zustand의 상태 관리에 DevTools 미들웨어가 추가된다.

    👉 Zustand 상태 관리 스토어의 모든 상태 변화가 Redux DevTools를 통해 추적 가능하게 된다. 
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';

const store = (set) => ({
  // 상태 및 메서드 정의
});

const useKanbanBoardStore = create(devtools(store));

 

 


 

 

Introduction - Zustand

How to use Zustand

zustand.docs.pmnd.rs

 

 

Introduction - Zustand

How to use Zustand

zustand.docs.pmnd.rs

 

 

Zustand | Notion

React 상태 관리 라이브러리

euid.notion.site

 

 

개쉽다! Zustand 사용법 - React 상태관리 라이브러리

현재 리액트 상태관리 라이브러리는 참 많이 있습니다. 대표적인 Redux와 MobX, Recoil, Jotai, ... Redux가 상태관리 라이브러리의 시초격(Flux 패턴)이라 할 수 있는데요. 그렇기 때문에 많은 개발자들로

blacklobster.tistory.com