스토어 만들기: create와 set, get
Zustand 스토어는 create 함수 하나로 만듭니다. 상태(state)와 그것을 바꾸는 함수(action)를 한 곳에 정의합니다.
기본 구조
import { create } from 'zustand';
const useStore = create((set, get) => ({
// 상태
count: 0,
name: '홍길동',
// 액션 (상태를 바꾸는 함수)
increment: () => set((state) => ({ count: state.count + 1 })),
setName: (name) => set({ name }),
}));create는 커스텀 훅을 반환합니다. 이 훅을 컴포넌트에서 호출해 상태와 액션을 가져옵니다.
set: 상태 업데이트
set은 스토어 상태를 업데이트하는 함수입니다. 현재 상태와 병합됩니다. (Object.assign처럼 동작)
const useStore = create((set) => ({
count: 0,
name: '홍길동',
// 방식 1: 새 값 직접 전달
setName: (name) => set({ name }), // count는 그대로 유지됨
// 방식 2: 함수로 이전 상태 참조
increment: () => set((state) => ({ count: state.count + 1 })),
}));이전 상태를 기반으로 업데이트할 때는 함수 형태를 씁니다. useState의 함수형 업데이트와 같은 이유입니다.
replace 옵션
기본 동작은 병합이지만, replace: true를 주면 상태 전체를 교체합니다.
// 기본: 병합 (count는 유지)
set({ name: '이순신' }); // { count: 0, name: '이순신' }
// replace: 전체 교체
set({ name: '이순신' }, true); // { name: '이순신' } (count 사라짐!)get: 다른 상태 참조
액션 안에서 현재 상태를 읽어야 할 때 get을 사용합니다.
const useStore = create((set, get) => ({
items: [],
selectedId: null,
// get으로 현재 상태 참조
getSelectedItem: () => {
const { items, selectedId } = get();
return items.find(item => item.id === selectedId);
},
// 조건부 업데이트
toggleItem: (id) => {
const { selectedId } = get();
set({ selectedId: selectedId === id ? null : id });
},
}));set의 함수 형태로도 상태를 참조할 수 있지만, get은 액션 함수 어디서든 현재 상태를 읽을 수 있어 더 유연합니다.
컴포넌트에서 사용
function Counter() {
// 필요한 상태/액션만 셀렉터로 가져오기
const count = useStore((state) => state.count);
const increment = useStore((state) => state.increment);
return (
<div>
<p>{count}</p>
<button onClick={increment}>+1</button>
</div>
);
}셀렉터 없이 전체를 가져올 수도 있습니다. 하지만 권장하지 않습니다.
// 비권장: 스토어 전체를 구독 → 어떤 상태가 바뀌어도 리렌더링
const { count, increment } = useStore();
// 권장: 필요한 것만 구독
const count = useStore((state) => state.count);실전 예제: 장바구니 스토어
const useCartStore = create((set, get) => ({
items: [],
addItem: (product) => {
const { items } = get();
const existing = items.find(item => item.id === product.id);
if (existing) {
// 이미 있으면 수량 증가
set({
items: items.map(item =>
item.id === product.id
? { ...item, quantity: item.quantity + 1 }
: item
),
});
} else {
// 없으면 추가
set({ items: [...items, { ...product, quantity: 1 }] });
}
},
removeItem: (id) => set((state) => ({
items: state.items.filter(item => item.id !== id),
})),
clearCart: () => set({ items: [] }),
getTotalPrice: () => {
const { items } = get();
return items.reduce((total, item) => total + item.price * item.quantity, 0);
},
}));
// 사용
function CartButton({ product }) {
const addItem = useCartStore((state) => state.addItem);
return <button onClick={() => addItem(product)}>장바구니 추가</button>;
}
function CartTotal() {
const getTotalPrice = useCartStore((state) => state.getTotalPrice);
return <div>총액: {getTotalPrice().toLocaleString()}원</div>;
}컴포넌트 외부에서 접근
// 스토어 인스턴스로 직접 접근
const state = useCartStore.getState();
state.addItem(product);
// 상태 변경 구독 (React 외부에서)
const unsubscribe = useCartStore.subscribe((state) => {
localStorage.setItem('cart', JSON.stringify(state.items));
});
// 구독 해제
unsubscribe();정리
| 항목 | 설명 |
|---|---|
create(fn) | 스토어 생성, 커스텀 훅 반환 |
set({ ... }) | 상태 부분 업데이트 (병합) |
set(fn) | 이전 상태 기반 업데이트 |
get() | 현재 상태 읽기 (액션 안에서) |
useStore(selector) | 컴포넌트에서 필요한 것만 구독 |
useStore.getState() | 컴포넌트 외부에서 상태 접근 |