概要
アプリケーションが崩壊する時、それは「状態の場所」と「状態の流れ」が破綻している。
状態とは、“何が変わるか”ではなく、“どこで持つべきか”を設計する行為である。
本稿では、JavaScript(およびReact/VueなどのSPA環境を前提)において、状態管理の判断軸と設計パターンを以下の観点で解説する:
- ローカル状態 vs グローバル状態の設計的判断
- Fluxアーキテクチャの本質
- 非同期ステート更新とイベント駆動の協調
- 状態スライスと責務分離
- 状態と副作用の分離設計
1. 状態の“責務”による分類
✅ ローカルステート
- UIに閉じている(フォームの開閉、アクティブタブ、ホバー状態)
- コンポーネントが完結している
✅ グローバルステート
- ユーザー認証、言語設定、カート情報、フィルタ条件
- 複数コンポーネント間で共有される“意味的状態”
→ 状態の“利用範囲”ではなく、“意味的責務”で判定すべき
2. Flux原則:状態設計の4原則
Action → Dispatcher → Store → View
- 単方向データフロー
- 状態は唯一の正解としてStoreに集約
- 更新は必ずAction経由
dispatch({ type: 'UPDATE_USER', payload: user });
→ ✅ “状態がどこで、どう変わるか”を1本道で追える構造
3. 状態スライスとスコープ分離
// userSlice.js
const initialState = { name: '', isLoggedIn: false };
function userReducer(state = initialState, action) {
switch (action.type) {
case 'LOGIN':
return { ...state, name: action.payload, isLoggedIn: true };
default:
return state;
}
}
- ✅ 状態はスライス(単位)ごとに切り出し
- ✅ reducer/Storeの責務をドメインごとに明確化
4. 非同期アクションと副作用の切り出し
async function loginUser(dispatch, credentials) {
dispatch({ type: 'LOGIN_REQUEST' });
try {
const user = await api.login(credentials);
dispatch({ type: 'LOGIN_SUCCESS', payload: user });
} catch (e) {
dispatch({ type: 'LOGIN_FAILURE', error: e });
}
}
- ✅ 副作用(API通信など)はAction内部 or Middlewareに委任
- ✅ reducerは純粋関数に保つことが原則
5. 状態フローの可視化とデバッグ
- Redux DevToolsやVue DevToolsで状態の履歴を追える構造
- “今の状態”ではなく、“状態がどう変化したか”が追えるように設計
- 状態更新の責務は1つの関数だけが持つように制御
6. 状態の“流れ”に関する設計指針
観点 | 意図 |
---|---|
単方向データフロー | どの順番で状態が変化するかを保証する |
イベント駆動 | 状態の変化はユーザー操作 or 副作用によって発火 |
状態の静的定義 | 状態の構造は明示的(初期状態・型など) |
ストアの分割 | 状態が増えてもスケーラブルに設計できるようにする |
設計判断フロー
① 状態はUIに閉じている? → ローカルstateで完結
② 状態が複数箇所に波及? → グローバルstateとして切り出す
③ 状態変更が曖昧になってきた? → Flux構造で流れを明示
④ 状態が副作用と混在? → 副作用はActionまたはMiddlewareに分離
⑤ 状態が追えない? → DevTools等で履歴をトラック可能に設計
よくあるミスと対策
❌ すべての状態をグローバルに持つ
→ ✅ 意味的責務に基づいて、スコープに応じて分離する
❌ Actionやreducerの肥大化
→ ✅ 状態スライスを責任範囲ごとにモジュール化する
❌ 状態変更がどこから行われているかわからない
→ ✅ “Actionを通す”ことを構造として強制する
結語
状態とは「今どうなっているか」ではなく、「どう変わってきたか」を設計する構造である。
- UIの背後にある意図
- 非同期による変化
- 操作に応じた反応
状態はコードの心拍である。
それを正しく設計することが、アプリケーションに“生命”を与える第一歩である。