
어떤 컴포넌트들이 리랜더링 되는지 확인 가능
const {increase, decrease} = useCountStore();
(⭐️⭐️⭐️count 값이 없더도 함수를 불러온 이상 불러온 것!⭐️⭐️⭐️)
===
const {increase, decrease, count} = useCountStore();
import { useCountStore } from "@/store/count";
import { Button } from "../ui/button";
export default function Controller() {
문제점이 많음
const {increase, decrease} = useCountStore();
return (
<div>
<Button onClick={increase}>+</Button>
<Button onClick={decrease}>-</Button>
</div>
);
}
const increase = useCountStore((store) => store.increase);
const decrease = useCountStore((store) => store.decrease);
export default function Controller() {
const increase = useCountStore((store) => store.increase);
const decrease = useCountStore((store) => store.decrease);
return (
<div>
<Button onClick={increase}>+</Button>
<Button onClick={decrease}>-</Button>
</div>
);
}

const { increase, decrease } = useCountStore((store) => store.actions);
// 카운트 값을 가져오는 훅
export const useCount = () => {
const count = useCountStore((store) => store.count);
return count;
};
export const useIncreaseCount = () => {
const increase = useCountStore((store) => store.actions.increase);
return increase;
};
export const useDecreaseCount = () => {
const decrease = useCountStore((store) => store.actions.decrease);
return decrease;
};
한 훅으로 모든 컴포넌트의 이름을 안 변경해도 된다!


// 결합하다.
import { combine } from "zustand/middleware";
create(
combine(
{
count: 0,
},
(set, get) => ({
actions: {
increaseOne: () => {
set((store) => ({ count: store.count + 1 }));
},
decreaseOne: () => {
set((store) => ({ count: store.count - 1 }));
},
},
}),
),
);
create(
combine(
{
count: 0,
},
(set, get) => ({
actions: {
increaseOne: () => {
get();
set((state) => ({ count: state.count + 1 }));
},
decreaseOne: () => {
set((state) => ({ count: state.count - 1 }));
},
},
}),
),
);
combine 미들웨어상태(state)와 액션(actions)의 명확한 분리
자동 타입 추론
코드 가독성 향상
첫 번째 인자는 초기 상태만
{ count: 0 } // ✅ 올바른 예
두 번째 인자는 액션 함수만
(set, get) => ({
actions: {
increase: () => set((state) => ({ count: state.count + 1 }))
}
})
초기값의 타입이 전체 타입을 결정
{ count: undefined } // ❌ 타입이 undefined로 고정됨
{ count: 0 } // ✅ number 타입으로 추론
첫 번째 인자의 값만 상태로 인식됨
import { create } from "zustand";
import { combine } from "zustand/middleware";
export const useCountStore = create(
combine(
// 1. 초기 상태
{
count: 0,
},
// 2. 액션 함수
(set, get) => ({
actions: {
increaseOne: () => set((state) => ({ count: state.count + 1 })),
decreaseOne: () => set((state) => ({ count: state.count - 1 })),
},
})
)
);
// 사용
export const useCount = () => useCountStore((store) => store.count);
export const useIncreaseCount = () => useCountStore((store) => store.actions.increaseOne);

immer 미들웨어불변성 자동 관리
...spread 연산자나 복잡한 복사 로직 불필요코드 간결화
실수 방지
반드시 immer를 가장 바깥쪽에 배치
// ✅ 올바른 순서
create(immer(combine(...)))
// ❌ 잘못된 순서
create(combine(immer(...)))
set 함수에서 return 하지 않기
// ✅ 올바른 사용 (값을 반환하지 않음)
set((state) => {
state.count += 1;
});
// ❌ 잘못된 사용 (객체를 반환하면 안됨)
set((state) => {
state.count += 1;
return state; // 반환하면 안됨!
});
원시 타입은 직접 반환해야 함
// ❌ 원시 타입은 직접 수정 불가
set((state) => {
state = newValue; // 작동하지 않음
});
// ✅ 객체로 감싸서 사용
set((state) => {
state.value = newValue;
});
async 함수 안에서 draft 사용 주의
// ❌ 비동기 이후 draft 수정
set(async (state) => {
await fetchData();
state.data = newData; // 위험!
});
// ✅ 비동기 처리 후 새로운 set 호출
const data = await fetchData();
set((state) => {
state.data = data;
});
import { create } from "zustand";
import { combine } from "zustand/middleware";
import { immer } from "zustand/middleware/immer";
export const useCountStore = create(
immer(
combine(
{
count: 0,
nested: {
value: 10,
items: [1, 2, 3],
},
},
(set, get) => ({
actions: {
// 간단한 업데이트
increaseOne: () => {
set((state) => {
state.count += 1; // 직접 수정
});
},
// 중첩된 객체 업데이트
updateNested: () => {
set((state) => {
state.nested.value = 20; // 간단!
});
},
// 배열 조작
addItem: (item: number) => {
set((state) => {
state.nested.items.push(item); // 직접 push!
});
},
removeItem: (index: number) => {
set((state) => {
state.nested.items.splice(index, 1); // 직접 splice!
});
},
},
})
)
)
);
import { create } from "zustand";
import { combine } from "zustand/middleware";
export const useCountStore = create(
combine(
{
count: 0,
nested: {
value: 10,
items: [1, 2, 3],
},
},
(set, get) => ({
actions: {
// 간단한 업데이트
increaseOne: () => {
set((state) => ({ count: state.count + 1 }));
},
// 중첩된 객체 업데이트 - 복잡함!
updateNested: () => {
set((state) => ({
nested: {
...state.nested,
value: 20,
},
}));
},
// 배열 조작 - 더 복잡함!
addItem: (item: number) => {
set((state) => ({
nested: {
...state.nested,
items: [...state.nested.items, item],
},
}));
},
removeItem: (index: number) => {
set((state) => ({
nested: {
...state.nested,
items: state.nested.items.filter((_, i) => i !== index),
},
}));
},
},
})
)
);
immer 사용 권장:
immer 미사용 권장:
두 미들웨어를 조합하면 최고의 개발 경험을 얻을 수 있습니다:
import { create } from "zustand";
import { combine } from "zustand/middleware";
import { immer } from "zustand/middleware/immer";
export const useStore = create(
immer( // ← 가장 바깥쪽에 immer
combine( // ← 안쪽에 combine
{ /* 초기 상태 */ },
(set, get) => ({ /* 액션 */ })
)
)
);
장점: