--- id: react-reducer-usereducer title: useReducer — 복잡 상태 전이 category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [react, reducer, state-machine, vibe-coding] tech_stack: { language: "TypeScript / React 18+", applicable_to: ["Web", "React Native"] } applied_in: [] aliases: [useReducer, dispatch, action type] --- # useReducer — 복잡 상태 전이 > 여러 setState 호출이 한 묶음으로 일어나거나, 다음 상태가 이전 상태에 의존하면 useReducer. **action 을 discriminated union 으로 정의**하면 state 전이가 곧 state machine. ## 📖 핵심 개념 - `(state, action) => state` 순수 함수. - 호출자는 dispatch(action) — UI 와 비즈니스 로직 분리. - 테스트 = reducer 함수만 단위 테스트. ## 💻 코드 패턴 ```ts type State = | { kind: 'idle' } | { kind: 'loading' } | { kind: 'success'; data: User } | { kind: 'error'; message: string }; type Action = | { type: 'fetch_start' } | { type: 'fetch_success'; data: User } | { type: 'fetch_error'; message: string } | { type: 'reset' }; function reducer(state: State, action: Action): State { switch (action.type) { case 'fetch_start': return { kind: 'loading' }; case 'fetch_success': return { kind: 'success', data: action.data }; case 'fetch_error': return { kind: 'error', message: action.message }; case 'reset': return { kind: 'idle' }; } } function User({ id }: { id: string }) { const [state, dispatch] = useReducer(reducer, { kind: 'idle' }); useEffect(() => { dispatch({ type: 'fetch_start' }); const ac = new AbortController(); fetch(`/api/users/${id}`, { signal: ac.signal }) .then(r => r.json()) .then(data => dispatch({ type: 'fetch_success', data })) .catch(e => { if (e.name !== 'AbortError') dispatch({ type: 'fetch_error', message: e.message }); }); return () => ac.abort(); }, [id]); switch (state.kind) { case 'idle': return null; case 'loading': return ; case 'success': return ; case 'error': return ; } } ``` ## 🤔 의사결정 기준 | 상황 | useReducer | useState | |---|---|---| | 상태 필드 1~2개 | ❌ | ✅ | | 다음 상태가 여러 필드 동시 변경 | ✅ | useState 도 가능하지만 reducer 가 명확 | | 상태 머신 (idle/loading/success) | ✅ | ❌ | | 깊은 자식이 dispatch 만 알면 됨 | ✅ (Context 로 dispatch 만 내려) | ❌ | | 외부 라이브러리 (Zustand) 더 나은 경우 | 글로벌 상태면 외부 store | useReducer 는 컴포넌트 단위 | ## ❌ 안티패턴 - **action 에 함수/promise 넣기**: action 은 직렬화 가능 데이터. side effect 는 effect 안에서 dispatch. - **action.type 을 string union 안 만듦**: typo 가능. union 으로. - **reducer 안에서 fetch / setTimeout**: pure 깨짐. effect 가 dispatch. - **state 직접 mutate (`state.foo = 1`)**: React 가 변화 감지 못 함. 항상 새 객체 반환. - **switch 에 default 없음**: 새 action 추가 시 누락 모름. assertNever 패턴. ## 🤖 LLM 활용 힌트 - "복잡 상태 전이 → discriminated union state + action, exhaustive switch" 명시. - Immer 사용 시 reducer 안에서 mutation OK (immer 가 immutable 변환). ## 🔗 관련 문서 - [[Tagged_Union_Discriminated_Types]] - [[Pure_Functions_in_Practice]]