[React] React.js 강좌 26. Zustand로 가벼운 전역 상태 관리하기
React에서 전역 상태를 관리할 때 보통 세 가지 선택지가 있습니다.
Context API, Redux, 그리고 최근 많이 쓰이는 Zustand.
그중에서도 Zustand는 “단순함”으로 승부하는 라이브러리입니다.
처음 Zustand를 접했을 땐,
‘이게 전역 상태 관리라고 할 수 있나?’ 싶을 정도로 코드가 간단했습니다.
하지만 몇 번 프로젝트에 적용해보니,
이 단순함이 얼마나 강력한 장점인지 알게 됐습니다.
왜 Zustand인가
Context API는 좋은 도구지만,
컴포넌트 트리 깊숙이 들어간 상태를 자주 갱신할 때 성능 문제가 생길 수 있습니다.
Redux는 기능은 강력하지만 설정이 복잡하고 코드가 장황해지기 쉽죠.
Zustand는 그 중간 지점에 서 있습니다.
가볍고 빠르며, 보일러플레이트 코드가 거의 없습니다.
설정도 단 한 줄이면 끝납니다.
무엇보다도 훅 기반이라 React스럽습니다.
별도의 Provider를 감싸지 않아도 되고,
필요한 곳에서 바로 상태를 가져다 쓸 수 있습니다.
Zustand 기본 사용법
Zustand를 설치합니다.
npm install zustand
이제 아주 간단한 카운터 상태를 만들어보겠습니다.
import { create } from 'zustand';
const useCounterStore = create((set) => ({
count: 0,
increase: () => set((state) => ({ count: state.count + 1 })),
decrease: () => set((state) => ({ count: state.count - 1 })),
}));
이제 어떤 컴포넌트에서도 아래처럼 바로 사용할 수 있습니다.
function Counter() {
const { count, increase, decrease } = useCounterStore();
return (
<div>
<h1>Count: {count}</h1>
<button onClick={increase}>+</button>
<button onClick={decrease}>-</button>
</div>
);
}
useCounterStore라는 훅을 통해 상태를 구독하고,
Zustand 내부에서 필요한 부분만 렌더링이 일어납니다.
이게 Context API보다 훨씬 효율적인 이유입니다.
Zustand의 구조적 강점
Zustand는 단순한 전역 상태 저장소 이상입니다.
아래와 같은 특징 덕분에 중형 규모의 프로젝트까지도 커버할 수 있습니다.
- 선택적 구독(Selective Subscription)
필요한 상태만 구독하기 때문에,
전체 스토어가 갱신되어도 관련 없는 컴포넌트는 리렌더링되지 않습니다. - const count = useCounterStore((state) => state.count);
- 미들웨어로 확장성 확보
persist, devtools, immer 같은 미들웨어를 추가해
상태 유지나 개발자 도구 연동도 가능합니다.이렇게 하면 브라우저를 새로고침해도
localStorage에 저장된 값이 자동으로 복원됩니다. - import { create } from 'zustand'; import { persist } from 'zustand/middleware'; const useAuthStore = create( persist( (set) => ({ token: null, setToken: (token) => set({ token }), }), { name: 'auth-storage' } ) );
- React 외부에서도 사용 가능
Zustand는 React에 종속되지 않습니다.
단순히 JS 함수 기반이라
백엔드 연동, WebSocket 데이터 관리에도 응용할 수 있습니다.
프로젝트에서 써보며 느낀 점
저는 Zustand를 React Query와 함께 썼을 때 가장 효율적이었습니다.
React Query는 서버 상태를,
Zustand는 클라이언트 상태를 담당하게 분리한 구조였죠.
예를 들어, 사용자 테마 모드나 모달 열림 상태 같은 UI 상태는 Zustand에,
서버에서 불러오는 데이터는 React Query에 맡깁니다.
const useUIStore = create((set) => ({
theme: 'light',
toggleTheme: () =>
set((state) => ({ theme: state.theme === 'light' ? 'dark' : 'light' })),
modalOpen: false,
setModalOpen: (value) => set({ modalOpen: value }),
}));
이런 구조로 나누면
각 상태가 명확한 책임을 가지게 되어 유지보수가 쉬워집니다.
Zustand는 전역 상태 관리라기보다
**“필요한 곳에 가볍게 붙일 수 있는 상태 저장소”**로 보는 게 맞습니다.
덕분에 불필요한 구조가 줄고, 개발 속도도 빨라집니다.
Zustand를 쓸 때 주의해야 할 점
Zustand는 너무 가볍다 보니
규모가 커질수록 상태의 구조를 명확히 설계하지 않으면
코드가 흩어질 위험이 있습니다.
예를 들어, 모든 상태를 한 파일에 몰아넣는 것은 좋지 않습니다.
useAuthStore, useUIStore, useModalStore처럼
기능별로 스토어를 분리해 관리하는 것이 이상적입니다.
또한 상태 의존성이 많은 복잡한 구조에서는
Redux Toolkit처럼 액션 흐름이 명확한 도구가 더 적합할 수도 있습니다.
Zustand를 처음 접했을 때는
“이렇게 단순한데 정말 실무에서 써도 되나?” 싶었지만,
결국 지금 제 대부분의 프로젝트는 Zustand를 기본으로 쓰고 있습니다.
빠르고, 가볍고, 설정이 거의 필요 없습니다.
React 18 이후의 함수형 개발 패턴과도 잘 어울리죠.
다음 글에서는 Zustand의 이런 구조를 기반으로,
React Query와 함께 결합해 완전한 상태 관리 아키텍처를 만드는 방법을 다뤄보겠습니다.
서버 상태와 클라이언트 상태를 각각 분리하면서도
자연스럽게 연동되는 패턴을 소개하겠습니다.