【複雑なStateを撃退!】React の useReducer で作る「予測可能」な状態管理。useState からの卒業
85日目にカスタムHookでロジックを切り離す技を学び、コードがだいぶスッキリしてきたはずです。
本日86日目は、Reactの状態管理の「もう一つの柱」、「useReducer と複雑な State 遷移」を学習します。 「Stateが複雑すぎて、あちこちで更新してバグが起きる…」そんな悩みを、「Reduxのミニチュア版」 とも言える useReducer で鮮やかに解決しましょう!
🏷️はじめに:その「useState」、絡まっていませんか?
useState は便利ですが、Stateの中にオブジェクトや配列があり、それを「条件によってあれこれ書き換える」ようになると、コードが複雑になりがちです。
「あれ?このタイミングでこの変数はどうなるんだっけ?」
「あちこちのボタンで同じような更新ロジックをコピペしている…」
そんな時、「Stateの変更方法を1箇所に集約する」 仕組みが useReducer です。
💡 1. useReducer の基本概念
useReducer は、「アクション(指示書)」を投げると、「Reducer(計算係)」 が今のStateを見て新しいStateを返す、というルールで動きます。
Dispatch: 「こういうことして!」という指示(Action)を投げる。
Reducer: 「なるほど、それなら今の状態はこう変わるね」と計算する。
State Update: 新しいStateに差し替わる。
💻 実装例:カウントアップ・ダウンをReducerで管理
const initialState = { count: 0 };
// 💡 Reducer: 「現在の状態」と「指示」を受け取って「新しい状態」を返すだけ
function reducer(state, action) {
switch (action.type) {
case 'increment': return { count: state.count + 1 };
case 'decrement': return { count: state.count - 1 };
case 'reset': return { count: 0 };
default: throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
{/* 💡 あとはdispatchを呼ぶだけ */}
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
</>
);
}
💡 2. なぜ useState ではなく useReducer なのか?
ロジックの分離: 更新処理(Reducer)をコンポーネントの外に出せるため、テストが非常に簡単になります。
予測可能性: 「ボタンA」を押しても「ボタンB」を押しても、同じ dispatch を使うので、状態遷移のルールが一貫します。
複雑性の解消: useEffect で無理やり複数のStateを同期させる必要がなくなり、1つのReducerで全ての状態を管理できます。
📝 86日目のまとめと課題
✅ 本日の習得ポイント
Reducer関数: 「今の状態 + 指示 = 次の状態」という純粋な計算処理。
Dispatch: コンポーネント側からは「何をしたいか(type)」を伝えるだけにする。
状態の集約: 複雑なデータ構造の更新を一手に引き受ける役割。
📝 86日目の課題
「Todoリスト」のような、追加・削除・完了といった複数の操作があるコンポーネントを useState で作っていたら、それを useReducer に書き換えてみてください。
Reducer関数だけを取り出して、Vitestで「正しいアクションを送れば正しいStateが返るか」をテストしてみてください(すごくテストしやすいはずです!)。
