📌 Zustand
: React에서 상태를 관리하기 위한 경량의 상태 관리 라이브러리
- 상태 스토어(store)를 쉽게 만들 수 있다.
- 상태 스토어(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')이 호출되었을 때, 상태가 어떻게 변화했는지를 시각적으로 확인할 수 있다. - 디버깅 용이
: 개발 중에 상태 변경의 흐름을 쉽게 파악할 수 있어, 디버깅이 더 편리해진다. - 시간 여행 디버깅
: 상태 변경 전후의 상태를 비교하거나, 이전 상태로 되돌아가서 다시 살펴볼 수 있는 기능이 있다.
- 상태 변경 추적
⏩ 사용
- import로 devtools() 불러오기
- 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