Zustand는 React에서 상태 관리를 단순하고 가볍게 만들기 위한 라이브러리이다.
Zustand는 store 딱 1개 만들고 set 사용 이라는 단순한 구조로 상태를 관리할 수 있다.
기존에 전역 변수를 관리하기 위해 Redux를 쓰면:
그러면 이제 Redux를 완전히 사용하지 않아도 되는가? 그건 아니다.
Redux는 여전히 중·대규모 팀 환경에서 많이 쓰인다.
왜냐면 Redux는 강한 규칙과 흐름이 정형화되어 있어서
팀원이 10명, 20명으로 늘어나도 코드 스타일이 크게 흔들리지 않기 때문이다.
게다가 디버깅 편의성, time-travel, Redux DevTools 같은 도구 지원도 매우 강력하다.
action 기반으로 상태 변화 흐름을 명확하게 추적할 수 있다는 점도 여전히 장점으로 남아 있다.
전역 상태 관리를 할 수 있는 라이브러리는 Zustand외에도 많이 있다
Recoil, Jotai, Redux Toolkit 등등 존재하지만,
현재 기준으로 Zustand가 가장 단순하고 쓰기 편하다고들 한다.
| 항목 | 설명 |
|---|---|
| 코드가 짧아짐 | Redux 대비 압도적으로 간결 |
| 글로벌 상태 쉽게 공유 | props drilling 해결 |
| immer optional | 불변성 유지 편함 |
| selector 사용 쉬움 | 렌더링 최적화 유리 |
| React Context 없이 가능 | Provider 필요 없음 |
import { create } from "zustand";
export const useCounterStore = create((set) => ({
count: 0,
increase: () => set((state)=>({ count: state.count + 1 })),
decrease: () => set((state)=>({ count: state.count - 1 })),
}));
그리고 컴포넌트에서
const { count, increase } = useCounterStore();
이걸로 전역 상태 끝.
Zustand는 상태와 로직을 분리하지 않는다.
→ 즉 “store가 domain-service” 역할까지 수행
const count = useCounterStore((state)=>state.count);
이러면 count가 바뀔 때만 렌더링됨
→ 글로벌 상태여도 불필요한 re-render 적음
전역 상태를 localStorage에 자동 저장 가능
import { create } from "zustand";
import { persist } from "zustand/middleware";
export const usePostStore = create(
persist(
(set) => ({
posts: [],
add:(text) => set((state)=>({ posts:[...state.posts,{id:Date.now(), text}] })),
}),
{ name:"post-storage" }
)
);
React에서 state는 원본을 직접 바꾸면 안 된다.
원본을 수정하지 않고, 새로운 객체를 만들어서 교체하는 방식을 요구한다.
immer는 귀찮은 불변성 처리를 대신해준다.
import { create } from "zustand";
import { produce } from "immer";
export const useTodo = create((set)=>({
todos:[],
toggle:(id)=>set(produce(state=>{
const t = state.todos.find(t=>t.id===id)
t.isDone = !t.isDone
}))
}))
위 코드는 실제로 이렇게 한 것과 같다.
toggle:(id)=>set((state)=>({
todos: state.todos.map(t =>
t.id===id ? {...t, isDone: !t.isDone} : t
)
}))
즉, immer는 직접 값을 바꾸는 것처럼 보이는 코드를 사용하더라도
실제로는 불변성을 유지한 새로운 state 객체를 생성해준다.
Redux 쓰던 시절엔 비동기 작업을 하려면
dispatch → action → reducer → thunk → middleware 이런 흐름이 필요했다.
API 호출하는 것도 thunk 라는 별도의 미들웨어를 설치해서 다뤘다.
Zustand는 그런 절차가 없이 store 안에서 함수 하나 만들어서
그 안에서 await 쓰면 그게 끝이다.
export const useUser = create((set)=>({
user:null,
fetchUser: async ()=> {
const res = await fetch("api/user");
const data = await res.json();
set({ user:data });
}
}));
| 기준 | Zustand | Redux |
|---|---|---|
| 코드량 | 적음 | 많음 |
| 러닝커브 | 쉬움 | 높음 |
| Provider 필요 | 없음 | 있음 |
| TS | 쉬움 | verbose |
| 대규모 팀 | 충분 | 규칙 필요시 여전히 선호 |
요약: Redux의 본질적 강점이 규칙으로 인한 안정성이라면
Zustand는 개발자가 똑똑하다고 가정하고 자유를 주는 것이다.
Zustand는 현대 React 전역상태의 default 선택지가 되었다.
store가 action과 state를 1개 파일에 모으고 single-source-of-truth 이 된다
그리고 selector 기반이라 렌더링도 효율적이다.
실제 프로젝트에서 어떤 라이브러리를 쓸지는 팀 상황과 목표에 따라 달라질 수 있겠지만,
개발 환경이 가볍고, 복잡한 설정 없이 빠르게 전역 상태를 도입하고 싶다면
Zustand는 충분히 좋은 시작점이 될 수 있다고 생각한다.