이 규칙은 GitHub에서 삭제됐어요. 참고용으로만 확인할 수 있어요.
description: "상태 관리 전략 — 서버 상태 vs 클라이언트 상태, Zustand/Context 사용 기준" paths:
상태를 먼저 분류하고, 그에 맞는 도구를 선택한다.
서버 상태 → TanStack Query (API 데이터, 캐싱, 동기화)
전역 UI 상태 → Zustand (여러 컴포넌트가 공유하는 UI 상태)
지역 상태 → useState / useReducer (컴포넌트 내부에서만 사용)
파생 상태 → 렌더링 중 계산 (다른 상태에서 도출 가능한 값)
API 데이터는 항상 TanStack Query로 관리. useEffect + useState로 fetch 금지.
→ 패턴은 tanstack-query.md 참고
✅ Zustand가 적합한 경우
- 사이드바 열림/닫힘 (여러 컴포넌트가 제어)
- 선택된 필터/탭 상태 (URL로 표현하기 애매한 경우)
- 토스트/알림 큐
- 모달 스택 관리
❌ Zustand 불필요한 경우
- 한 컴포넌트에서만 쓰는 상태 → useState
- API 데이터 → TanStack Query
- URL에 반영되어야 하는 상태 → searchParams
// store/ui-store.ts
import { create } from 'zustand'
interface UiStore {
sidebarOpen: boolean
toggleSidebar: () => void
setSidebarOpen: (open: boolean) => void
}
export const useUiStore = create<UiStore>((set) => ({
sidebarOpen: true,
toggleSidebar: () => set((s) => ({ sidebarOpen: !s.sidebarOpen })),
setSidebarOpen: (open) => set({ sidebarOpen: open }),
}))
store/ 또는 shared/store/ 에 위치use[Domain]Store// ✅ 필요한 것만 선택 (불필요한 리렌더 방지)
const sidebarOpen = useUiStore((s) => s.sidebarOpen)
// ❌ 스토어 전체 구독
const store = useUiStore()
단순한 단일 값, 독립적인 상태.
const [isOpen, setIsOpen] = useState(false)
const [inputValue, setInputValue] = useState('')
관련된 상태가 여러 개거나, 다음 상태가 이전 상태에 의존하는 경우.
type Action =
| { type: 'SET_STEP'; step: number }
| { type: 'NEXT' }
| { type: 'RESET' }
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'NEXT': return { ...state, step: state.step + 1 }
case 'RESET': return initialState
default: return state
}
}
→ 자세한 기준은 react-hooks.md 참고
Zustand 없이 Props Drilling만 해결하면 될 때.
✅ Context가 적합한 경우
- 테마 (다크/라이트)
- 현재 로그인 유저 정보 (읽기 전용)
- 특정 컴포넌트 트리에서만 공유되는 설정값
❌ Context 부적합한 경우
- 자주 변경되는 상태 (리렌더 폭탄)
- 전역에서 쓰는 UI 상태 → Zustand
- 서버 데이터 → TanStack Query
const ThemeContext = createContext<'light' | 'dark'>('light')
export function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState<'light' | 'dark'>('light')
return <ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>
}
export const useTheme = () => useContext(ThemeContext)
다른 상태에서 계산할 수 있는 값은 상태로 만들지 않는다.
// ❌ 상태 중복
const [items, setItems] = useState<Item[]>([])
const [count, setCount] = useState(0) // items.length와 동기화 필요
// ✅ 렌더링 중 계산
const [items, setItems] = useState<Item[]>([])
const count = items.length
// 비싼 계산만 useMemo
const sortedItems = useMemo(() => [...items].sort(...), [items])
필터, 정렬, 페이지 등 공유/북마크 가능해야 하는 상태는 URL에.
// searchParams 활용 (Next.js)
const searchParams = useSearchParams()
const sort = searchParams.get('sort') ?? 'latest'
아직 피드백이 없어요. 첫 번째로 의견을 남겨보세요!