概要
状態管理とは「変数に値を持たせること」ではない。
それは**“状態の意味・遷移の可能性・UIの整合性を制御するための構造的制約”**である。
状態が「自由すぎる」ことで、**「ローディング中にボタンが押せる」「エラーなのに結果が表示されている」「完了後に再度送信される」**といった 「状態爆発」 が発生する。
この問題を解決するには、状態を 列挙・制御・可視化可能な構造として設計することが重要である。
1. 状態遷移の定義と「状態爆発」の理解
// NG: 多値フラグによる組み合わせ爆発
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const [completed, setCompleted] = useState(false);
- ❌ フラグの組み合わせが無限に広がり、UIとの整合性が崩壊
2. 列挙型による状態の一元管理
type FormState = 'idle' | 'loading' | 'error' | 'success';
const [state, setState] = useState<FormState>('idle');
- ✅ 状態は「意味のある列挙値」で持つ
- ✅ フロー上の状態遷移図に従った移行のみを許可
3. 遷移を制御するステートマシン的構造
const transition = {
idle: ['loading'],
loading: ['success', 'error'],
error: ['loading'],
success: [],
};
function canTransition(from: FormState, to: FormState) {
return transition[from].includes(to);
}
- ✅ 不正な遷移(例:
success → loading
)を防ぐ - ✅ 状態と遷移を構造として宣言
4. UIは状態ごとのレンダリング責務を分離
switch (state) {
case 'idle':
return <SubmitButton />;
case 'loading':
return <LoadingIndicator />;
case 'error':
return <ErrorBanner onRetry={submitAgain} />;
case 'success':
return <ThankYouMessage />;
}
- ✅ 各状態ごとに UI責務を切り分ける
- ✅
if-else
や条件フラグではなく、状態 = UI の構造を明示
5. 状態モデルの単位とスコープ設計
- ページ単位(例: 読み込み状態, 完了状態)
- セクション単位(例: 投稿フォーム, 検索結果)
- エンティティ単位(例: 各ユーザーのローディング / フォロー状態)
- ✅ 状態モデルはスコープごとに設計
- ✅ 複数コンポーネントに影響を与えるものは store管理やstate machine導入
6. ステートマシンライブラリの導入(例: XState)
import { createMachine } from 'xstate';
const formMachine = createMachine({
id: 'form',
initial: 'idle',
states: {
idle: { on: { SUBMIT: 'loading' } },
loading: {
on: {
SUCCESS: 'success',
FAILURE: 'error',
},
},
error: { on: { RETRY: 'loading' } },
success: {},
},
});
- ✅ 宣言的に状態と遷移を定義可能
- ✅ 実行不能な状態遷移が構造的にブロックされる
設計判断フロー
① 状態は列挙型で表現されているか? → 多フラグの爆発を防げているか?
② 状態間の遷移が意図的に制御されているか?
③ UIの各部品は状態と責務が1対1で対応しているか?
④ 状態が変更されたとき、他UIに破壊的影響が出ない設計か?
⑤ 状態の定義と遷移ルールがコード上で明示されているか?
よくあるミスと対策
❌ 複数のフラグで状態を表現し、矛盾したUIになる
→ ✅ 状態を列挙型で一本化し、単一の責務を持たせる
❌ 状態遷移が暗黙的で、何が起きているかわからない
→ ✅ ステートマシン or トランジション定義で明示化
❌ UIで「完了後に再送信できてしまう」などの不整合が発生
→ ✅ 遷移元・遷移先の制限を構造的に設計
結語
状態管理とは「状態を持つこと」ではない。
それは**“変化を制御し、流れを制限し、UIと機能の整合性を守るための設計戦略”**である。
- 状態を列挙型で管理し
- 遷移を定義し、構造的に制約を与え
- UIと状態を1対1で対応させ
- ステートマシンを使い、爆発する組み合わせを制御する
JavaScriptにおける状態遷移設計とは、
“UIとロジックの整合性を保証し、複雑性に耐えるアプリの設計基盤”である。