
📅 2025-11-21
➡️ Zustand에 대해 새롭게 알게 된 것 또는 헷갈리는 부분 정리
리액트 애플리케이션에서 상태(state)는 화면(UI)에 보여지는 데이터의 현재 상태를 의미
상태가 변경되면, 그에 맞게 UI도 함께 업데이트 되어야 함
규모가 커질수록 문제 등장
단순 useState 로는 복잡한 애플리케이션을 관리하기 어려움 → 전역 상태 관리 필요
가장 대표적인 상태 라이브러리 Redux
장점
단점
🔗 https://zustand-demo.pmnd.rs/
단순화된 Flux 패턴을 사용하는 작고(small) 빠르고(fast) 확장가능한(scalable) 상태관리 솔루션이며, Hooks에 기반으로하는 간편한 API가 존재함
간결한 API
create 하나로 스토어를 만들고, Hook처럼 불러다 쓰는 구조성능 최적화 - 구독 기반 렌더링
리액트와의 자연스러운 통합
useState, useEffect를 사용하는 것과 비슷하게 Hook처럼 사용const bears = useBearsStore((state) => state.bears);
빠른 성장과 점유율
npm i zustand
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;import useStore from './store'; // Zustand 스토어를 불러옴
function Counter() {
const count = useStore((state) => state.count); // 상태를 불러옴
const increment = useStore((state) => state.increment); // 상태 변경 함수
const decrement = useStore((state) => state.decrement); // 상태 변경 함수
// 또는 구조 분해 할당
// const { count, increment, decrement } = useStore()
return (
<div>
<h1>카운터: {count}</h1>
<button onClick={increment}>증가</button>
<button onClick={decrement}>감소</button>
</div>
);
}
export default Counter;create: Zustand 스토어 생성하는 함수로, 상태와 그 상태를 업데이트 하는 액션을 정의하여 전역 상태 훅을 반환set: 상태를 업데이트하는 내장 함수count) 와 액션(increment, decrement)을 한 곳에서 정의Zustand
import { create } from "zustand";
const useStore = create((set) => ({
bears: 0,
increasePopulation: () =>
set((state) => ({ bears: state.bears + 1 })),
}));
export default useStore;
스토어와 액션을 한 파일에서 정의할 수 있어 구조가 단순함
Provider, reducer, action 타입 등 불필요
필요한 상태만 선택적으로 구독 → 불필요한 리렌더링 방지
장점
단점
Redux Toolkit 예시
import { configureStore, createSlice } from "@reduxjs/toolkit";
const slice = createSlice({
name: "counter",
initialState: { value: 0 },
reducers: {
increment: (state) => {
state.value += 1;
},
},
});
const store = configureStore({ reducer: slice.reducer });
export default store;
store, slice, reducer, provider 구조가 명확함
장점
단점
Zustand의 set함수는 기본적으로 불변성을 지키는 업데이트를 전제로 동작
하지만 중첩된 객체/배열을 직접 수정하면, 참조가 그대로라 React가 상태 변경을 감지하지 못함
// 권장되지 않는 패턴 (불변성 깨짐)
const useTodosStore = create((set) => ({
todos: [],
addTodo: (text) =>
set((state) => {
state.todos.push({
id: Date.now(),
text,
completed: false,
}); // 원본 배열 직접 수정
return state; // 같은 참조를 그대로 반환 → 리렌더링 안됨
}),
}));
state가 “같은 객체”라고 판단해서 UI 업데이트를 하지 않음immer를 이용한 해결
직접 객체를 수정하는 것처럼 코드를 작성하되, 내부적으로 불변성을 깨지 않도록 새로운 상태를 만들어줌
import { create } from "zustand";
import { immer } from "zustand/middleware/immer";
const useTodoStore = create(
immer((set) => ({
todos: [],
addTodo: (todo) => set((state) => ({ todos: [...state.todos, todo] })),
removeTodo: (index) =>
set((state) => ({
todos: state.todos.filter((_, i) => i !== index),
})),
}))
);
export default useTodoStore;
addTodo: (todo) =>
set((state) => {
state.todos.push(todo) // 불변성 걱정 없이 직접 변경 가능
}),
removeTodo: (index) =>
set((state) => {
state.todos.splice(index, 1) // 불변성 걱정 없이 직접 변경 가능
}),
불변성 신경 안써도 됨
코드 간결 & 실수 줄어둚
복잡한 중첩 구조에도 유리함
persist - 상태 영구 저장
persist는 상태를 localStorage / sessionStorage에 저장하여 새로고침하거나 브라우저를 닫아도 상태를 유지할 수 있게 해줌zustand/middleware에서 제공persist(스토어, 옵션)
name: 저장될 key 이름getStorage: 저장소 지정localStoragesessionStorage에 저장하고 싶을 때 → getStorage : () ⇒ sessionStoragepersist + immer 기본 패턴import { create } from "zustand";
import { persist } from "zustand/middleware";
import { immer } from "zustand/middleware/immer";
const useTodoStore = create(
persist(
immer((set) => ({
todos: [],
addTodo: (todo) =>
set((state) => {
state.todos.push(todo);
}),
removeTodo: (index) =>
set((state) => {
state.todos.splice(index, 1);
}),
})),
{
name: "todo-storage", // localStorage key 이름
getStorage : ()=> seessionStorage // 세션 스토리지에 저장하고 싶을때
}
)
);
export default useTodoStore;persist → immer 구조로 하는게 일반적임Devtools
🔗 https://chromewebstore.google.com/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd
- Zustand도 Redux DevTools와 연동 가능하며, 액션 이름을 명시해주면 디버깅이 매우 쉬워짐
import { create } from "zustand";
import { persist, devtools } from "zustand/middleware";
import { immer } from "zustand/middleware/immer";
const useTodoStore = create(
devtools(
persist(
immer((set) => ({
todos: [],
addTodo: (todo) =>
set((state) => {
state.todos.push(todo);
}, false, "ADD_TODO"), // DevTools에 표시될 액션명
removeTodo: (index) =>
set((state) => {
state.todos.splice(index, 1);
}, false, "REMOVE_TODO"), // DevTools에 표시될 액션명
})),
{ name: "todo-storage" }
)
)
);
export default useTodoStore;