アクションは何が起きたのかを説明する平易なオブジェクトであり、リデューサーは現在の状態とアクションを受け取り、次の状態を返す純粋関数です。これらは一緒に予測可能な状態遷移を実装します。
アクション — イベントを説明する
js
{ : , : { : , : } }
{ : , : }
アクションは何も実行しません。イベントの宣言的な説明です。過去形の事実(「added」「toggled」)として名付けることは、起きたことを記録していることを反映しています。
function todosReducer(state = [], action) {
switch (action.type) {
case "todos/added":
return [...state, action.payload]; // new array
case "todos/toggled":
return state.map(t => // new array, new object for the changed one
t.id === action.payload ? { ...t, done: !t.done } : t
);
default:
return state; // unknown action → unchanged
}
}
リデューサーのシグネチャは(state, action) => newStateです。新しい状態オブジェクト/配列を返す(イミュータブルに)必要があり、入力を変異させてはいけません。
純粋関数とは、(1)同じ入力に対して同じ出力を返し、(2)副作用がないことです。リデューサーは両方のルールに従う必要があります。
// ❌ NOT pure — breaks everything
function badReducer(state, action) {
state.value++; // ❌ mutates input (breaks change detection, time-travel)
fetch("/api/log"); // ❌ side effect (non-deterministic, untestable)
return { value: Math.random() }; // ❌ non-deterministic output
}
純粋性はReduxの特徴的な機能を実現します:
✓ Predictability — same state + action ALWAYS gives the same result
✓ Time-travel / replay — you can re-run actions to reproduce any state
✓ Testability — just call reducer(state, action) and assert; no mocks needed
✓ Change detection — returning new references lets the UI detect changes
副作用(APIコール、ロギング、ランダム性)はミドルウェア(thunks/sagas)やエフェクトに属します。リデューサーには絶対に属しません。
アクション/リデューサーモデルはReduxスタイルの状態管理の心臓であり、それがなぜこんなにデバッグとテストが容易なのかの理由です。
純粋性の要件は恣意的ではありません。タイムトラベルデバッグ、アクションリプレイ、自明なユニットテストが可能になり、副作用が他の場所に存在しなければならない理由です。
この純粋関数駆動のイミュータブル更新パターンはRedux、NgRx、useReducer、およびそれ以上に繰り返されます。それを理解し(リデューサーを純粋に保つ規律)、予測可能な状態の基本です。