Files
2nd/10_Wiki/Topics/Architecture/상태 머신 (State Machine) 모델링 및 Redux 액션_리듀서 설계.md
T
2026-05-10 22:08:15 +09:00

207 lines
6.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: wiki-2026-0508-상태-머신-state-machine-모델링-및-redux-
title: 상태 머신 (State Machine) 모델링 및 Redux 액션/리듀서 설계
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [State Machine + Redux, FSM Redux Design, XState + Redux]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [state-machine, redux, xstate, frontend, architecture]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
language: typescript
framework: redux-toolkit
---
# 상태 머신 (State Machine) 모델링 및 Redux 액션/리듀서 설계
## 매 한 줄
> **"매 impossible state 의 unrepresentable 화"**. 매 finite state machine (FSM) 는 explicit state + event + transition 의 enumerate. Redux reducer 는 매 (state, action) → state 의 pure function — 매 FSM 와 isomorphic. 2026 년 매 XState 5 + RTK 의 combine 패턴 의 standard.
## 매 핵심
### 매 FSM 5-tuple
- **States (Q)**: enumerable set — `idle | loading | success | error`.
- **Events (Σ)**: external triggers — `FETCH | RESOLVE | REJECT`.
- **Transitions (δ)**: `Q × Σ → Q` — 매 partial function.
- **Initial state (q₀)**: `idle`.
- **Final states (F)**: optional — terminal nodes.
### 매 Redux 와 mapping
- **Reducer = δ**: `(state, action) => newState` 의 pure transition.
- **Action = Σ event**: typed discriminated union.
- **Store state = Q**: 매 status field 의 enum.
- **Selector**: 매 derived view — `isLoading = state.status === 'loading'`.
### 매 응용
1. Form wizard — multi-step flow 의 state 의 explicit.
2. Data fetching — idle/loading/success/error 의 4-state.
3. Auth flow — anonymous/authenticating/authenticated/expired.
## 💻 패턴
### Discriminated union state
```typescript
type FetchState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error };
// 매 impossible state ('loading' + data) 의 unrepresentable.
```
### RTK slice = FSM reducer
```typescript
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
const userSlice = createSlice({
name: 'user',
initialState: { status: 'idle' } as FetchState<User>,
reducers: {
fetch: (state) => {
if (state.status === 'idle' || state.status === 'error') {
return { status: 'loading' };
}
return state; // ignore invalid transition
},
resolve: (_, action: PayloadAction<User>) => ({
status: 'success',
data: action.payload,
}),
reject: (_, action: PayloadAction<Error>) => ({
status: 'error',
error: action.payload,
}),
},
});
```
### Transition guard table
```typescript
const TRANSITIONS = {
idle: { FETCH: 'loading' },
loading: { RESOLVE: 'success', REJECT: 'error' },
success: { FETCH: 'loading' },
error: { FETCH: 'loading' },
} as const;
function transition<S extends keyof typeof TRANSITIONS>(
state: S,
event: string
): string {
return (TRANSITIONS[state] as any)[event] ?? state;
}
```
### XState 5 + Redux integration
```typescript
import { createMachine, createActor } from 'xstate';
const fetchMachine = createMachine({
id: 'fetch',
initial: 'idle',
states: {
idle: { on: { FETCH: 'loading' } },
loading: {
invoke: {
src: 'fetchUser',
onDone: { target: 'success', actions: 'storeData' },
onError: 'error',
},
},
success: { on: { REFRESH: 'loading' } },
error: { on: { RETRY: 'loading' } },
},
});
const actor = createActor(fetchMachine).start();
actor.subscribe((snapshot) => store.dispatch({ type: 'fsm/sync', payload: snapshot.value }));
```
### Hierarchical (nested) state
```typescript
// 매 auth flow — 매 sub-state machine.
const authMachine = createMachine({
initial: 'unauthenticated',
states: {
unauthenticated: {
initial: 'idle',
states: {
idle: { on: { LOGIN: 'submitting' } },
submitting: { on: { SUCCESS: '#auth.authenticated', FAIL: 'idle' } },
},
},
authenticated: {
on: { LOGOUT: 'unauthenticated' },
},
},
}, { id: 'auth' });
```
### Parallel state regions
```typescript
const editorMachine = createMachine({
type: 'parallel',
states: {
document: { initial: 'clean', states: { clean: {}, dirty: {} } },
network: { initial: 'online', states: { online: {}, offline: {} } },
},
});
// 매 state = (document, network) 의 cross product — explicit.
```
### Test as transition table
```typescript
test.each([
['idle', 'FETCH', 'loading'],
['loading', 'RESOLVE', 'success'],
['loading', 'REJECT', 'error'],
['success', 'FETCH', 'loading'],
])('transition %s --%s--> %s', (from, event, to) => {
expect(transition(from, event)).toBe(to);
});
```
## 매 결정 기준
| 상황 | Approach |
|---|---|
| Simple async (fetch) | RTK `createAsyncThunk` + discriminated union |
| Multi-step wizard | XState machine |
| Complex auth | XState hierarchical machine |
| Parallel concerns | XState parallel states |
| Pure UI toggle | useState (not Redux) |
**기본값**: RTK + discriminated union state. 복잡 시 XState 5.
## 🔗 Graph
- 부모: [[Finite State Machine]] · [[Redux]]
- 변형: [[XState]] · [[Statecharts]]
- 응용: [[Form Wizard]] · [[Data Fetching]] · [[Auth Flow]]
- Adjacent: [[Discriminated Union]] · [[Pure Function]] · [[Event Sourcing]]
## 🤖 LLM 활용
**언제**: state 가 3+ 개 + transition 의 explicit rule 의 필요 시.
**언제 X**: simple boolean toggle 또는 single-shot async — 매 overkill.
## ❌ 안티패턴
- **Boolean explosion**: `isLoading + isError + isSuccess` — impossible state (true+true) 의 representable.
- **Implicit transition**: reducer 안 valid state check 의 X — 매 invalid transition 의 silently 발생.
- **God reducer**: 매 모든 logic 의 한 reducer — 매 split.
- **Side effect in reducer**: `fetch()` 의 reducer 내부 — 매 thunk/saga/middleware 사용.
## 🧪 검증 / 중복
- Verified (Hopcroft & Ullman *Automata Theory*; Redux docs; XState v5 docs 2026).
- 신뢰도 A.
## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — FSM↔Redux mapping + XState 5 patterns |