728x90
반응형
Zustand를 쓰다 보면 상태 값과 setter(액션)를 어떻게 꺼내 쓰는 게 가장 효율적인지 고민이 생깁니다.
이번 글에서는 제가 정리한 값/세터 패턴, shallow 비교의 의미, 그리고 커스텀 훅으로 깔끔하게 묶는 방법을 공유합니다.
1. 값 꺼내기: 3가지 방식
const { viewMode } = useUIStore();
const viewMode2 = useUIStore((s) => s.viewMode);
const { viewMode: viewMode3 } = useUIStore.getState();
(1) `const { viewMode } = useUIStore();`
- 스토어 전체 구독 → 어떤 값이든 바뀌면 리렌더
- 단순하지만 불필요한 리렌더 위험
(2) `const viewMode2 = useUIStore((s) => s.viewMode);`
- selector로 필요한 값만 구독
- 값이 바뀔 때만 리렌더 → 가장 권장되는 방식
(3) `const { viewMode: viewMode3 } = useUIStore.getState();`
- 구독 없이 현재 값 스냅샷만 읽음
- 값이 바뀌어도 리렌더 안 됨
- 이벤트 핸들러, 비동기 로직에서만 사용
2. 여러 값 구독 시 shallow 비교
import { shallow } from "zustand/shallow";
const { viewMode, theme } = useUIStore(
(s) => ({ viewMode: s.viewMode, theme: s.theme }),
shallow
);
- `{...}` 객체는 매번 새로 생성 → shallow 없으면 리렌더 과다
- shallow 비교는 1단계 값만 비교해서 불필요한 리렌더 막아줌
- 💡 shallow를 쓰면 내부적으로 각각 셀렉터로 구독한 것과 동일한 효과입니다.
즉, 아래와 같은 개별 구독과 리렌더 동작이 동일합니다 👇
const viewMode = useUIStore((s) => s.viewMode);
const theme = useUIStore((s) => s.theme);
3. 세터(액션) 뽑기
세터는 함수 참조가 변하지 않지만,
`useUIStore()` 빈 호출로 구조분해하면 스토어 전체를 구독하게 되어 손해입니다.
👉 따라서 세터도 셀렉터로 뽑는 게 가장 안전합니다.
// 권장
const setViewMode = useUIStore((s) => s.setViewMode);
// 일회성 (이벤트 핸들러 등)
useUIStore.getState().setViewMode("dark");
4. 커스텀 훅으로 깔끔하게 묶기
스토어가 여러 개이거나, 셀렉터 호출이 많아지면
하나의 커스텀 훅으로 정리하는 것이 좋습니다.
예를 들어 UI, 테마, 사용자 스토어가 각각 있다면:
// useAppUI.ts
import { shallow } from "zustand/shallow";
import { useUIStore } from "@/stores/ui";
import { useThemeStore } from "@/stores/theme";
import { useUserStore } from "@/stores/user";
export function useAppUI() {
const viewMode = useUIStore((s) => s.viewMode);
const theme = useThemeStore((s) => s.theme);
const user = useUserStore((s) => s.user);
const setViewMode = useUIStore((s) => s.setViewMode);
return { viewMode, theme, user, setViewMode };
}
이제 컴포넌트에서는 간단히 사용합니다:
const { viewMode, theme, user, setViewMode } = useAppUI();
✅ 장점
- import가 간결해짐
- 각 스토어 구독은 여전히 개별 셀렉터 단위 → 리렌더 영향 없음
- 스토어 구조 변경 시 훅 내부만 수정하면 됨
💡 shallow는 한 스토어 내 여러 속성을 묶을 때만 의미가 있고,
스토어 간엔 필요 없습니다. (각 스토어가 자체적으로 독립 구독을 관리하기 때문이에요.)
5. 추천 패턴 요약
상황 | 추천 방식 |
---|---|
값 1개 | `useUIStore((s) => s.value)` |
값 여러 개 | `useUIStore((s) => ({ a: s.a, b: s.b }), shallow)` |
세터 | `useUIStore((s) => s.setValue)` |
비반응 스냅샷 | `useUIStore.getState()` |
여러 스토어 | 커스텀 훅(`useAppUI()`)으로 묶기 |
6. 결론
✅ 값은 selector로
✅ 여러 값은 selector + shallow로 (→ 각각 구독한 것과 동일 효과)
✅ 세터도 selector로
✅ 여러 스토어는 커스텀 훅으로 정리
✅ 일회성은 getState()
Zustand는 단순하지만 유연합니다.
핵심은 “최소 구독 + 명확한 의도”를 유지하는 것.
이 글이 여러분의 스토어 구조를 더 깔끔하게 정리하는 데 도움이 되길 바랍니다 🚀
반응형
'Googling > React + Next + Eco' 카테고리의 다른 글
Next.js 배럴(Barrel) 파일 가이드 (14+) (0) | 2025.10.09 |
---|