--- id: wiki-2026-0508-discriminated-unions-for-state-m title: Discriminated Unions for State Modeling category: 10_Wiki/Topics status: verified canonical_id: self aliases: [Tagged Unions, Sum Types, ADT State] duplicate_of: none source_trust_level: A confidence_score: 0.9 verification_status: applied tags: [typescript, state-modeling, types, frontend] raw_sources: [] last_reinforced: 2026-05-10 github_commit: pending tech_stack: language: TypeScript framework: React / any --- # Discriminated Unions for State Modeling ## 매 한 줄 > **"매 impossible state 의 unrepresentable"**. Discriminated union (tagged union, sum type) 으로 매 async/UI state 를 modeling 하면 매 `loading + error + data` simultaneous 같은 illegal combination 매 type system 매 차단. ## 매 핵심 ### 매 motivation - Boolean flag combinatorics: `isLoading`, `isError`, `data` → 8 states, 매 5+ illegal. - DU: 매 mutually-exclusive variants 만 표현. ### 매 anatomy - Common discriminator field (`type`, `status`, `kind`). - Per-variant payload. - TS narrows by discriminator in `switch` / `if`. ### 매 응용 1. Async data (idle / loading / success / error). 2. Form (initial / submitting / submitted / failed). 3. Wizard (step1 / step2 / done). 4. Auth (anonymous / loading / authenticated / expired). 5. Reducer/state machine actions. ## 💻 패턴 ### Async state DU ```ts type AsyncState = | { status: 'idle' } | { status: 'loading' } | { status: 'success'; data: T } | { status: 'error'; error: E }; function render(s: AsyncState) { switch (s.status) { case 'idle': return ; case 'loading': return ; case 'success': return ; // 매 data 매 type-safe case 'error': return ; } } ``` ### Exhaustive check helper ```ts function assertNever(x: never): never { throw new Error(`Unhandled variant: ${JSON.stringify(x)}`); } function step(s: WizardState) { switch (s.kind) { case 'address': return ; case 'payment': return ; case 'review': return ; default: return assertNever(s); // 매 compile error 매 새 variant 추가 시 } } ``` ### Form state DU ```ts type FormState> = | { phase: 'editing'; values: V } | { phase: 'submitting'; values: V } | { phase: 'failed'; values: V; errors: E } | { phase: 'submitted'; result: { id: string } }; ``` ### Auth state DU ```ts type Auth = | { state: 'anonymous' } | { state: 'authenticating' } | { state: 'authenticated'; user: User; token: string } | { state: 'expired'; lastUser: User }; function canAccessAdmin(a: Auth): boolean { return a.state === 'authenticated' && a.user.role === 'admin'; } ``` ### Reducer with action DU ```ts type Action = | { type: 'fetch/start' } | { type: 'fetch/success'; payload: User[] } | { type: 'fetch/error'; error: string }; function reducer(s: AsyncState, a: Action): AsyncState { switch (a.type) { case 'fetch/start': return { status: 'loading' }; case 'fetch/success': return { status: 'success', data: a.payload }; case 'fetch/error': return { status: 'error', error: new Error(a.error) }; } } ``` ### Pattern matching with `ts-pattern` ```ts import { match } from 'ts-pattern'; const view = match(state) .with({ status: 'idle' }, () => ) .with({ status: 'loading' }, () => ) .with({ status: 'success' }, ({ data }) => ) .with({ status: 'error' }, ({ error }) => ) .exhaustive(); ``` ### Nested DU (loading sub-status) ```ts type Resource = | { state: 'idle' } | { state: 'loading'; progress?: number } | { state: 'streaming'; partial: T[]; progress: number } | { state: 'success'; data: T } | { state: 'error'; error: Error; retryCount: number }; ``` ### Builder helpers ```ts const idle = (): AsyncState => ({ status: 'idle' }); const loading = (): AsyncState => ({ status: 'loading' }); const success = (data: T): AsyncState => ({ status: 'success', data }); const failure = (error: Error): AsyncState => ({ status: 'error', error }); ``` ## 매 결정 기준 | 상황 | Approach | |---|---| | 2+ boolean flags 매 mutually exclusive | DU 강제 | | Single boolean | 매 plain bool OK | | Complex transitions | DU + state machine (XState) | | Library API surface | DU 매 caller-friendly | **기본값**: async/form/wizard/auth state 매 즉시 DU 모델링. ## 🔗 Graph - 부모: [[TypeScript]] · [[Type-Driven-Design]] - 변형: [[Algebraic-Data-Types]] · [[Sum-Types]] - 응용: [[XState]] · [[React-Query]] - Adjacent: [[Exhaustiveness-Checking]] · [[Pattern-Matching]] ## 🤖 LLM 활용 **언제**: async data, multi-step UI, auth flow, anywhere 매 boolean explosion 발생. **언제 X**: 매 single-flag 매 trivial — over-engineering. ## ❌ 안티패턴 - **Boolean explosion**: `isLoading && !isError && data` chains. - **Optional fields encoding state**: `{ data?, loading?, error? }` — 매 invalid combos 가능. - **Missing exhaustive check**: 매 new variant 추가 시 매 silently broken. ## 🧪 검증 / 중복 - Verified (TS handbook / Richard Feldman "Making Impossible States Impossible"). - 신뢰도 A. ## 🕓 Changelog | 날짜 | 변경 | |---|---| | 2026-05-08 | Phase 1 | | 2026-05-10 | Manual cleanup — DU patterns for async/form/auth state |