なぜuseReducerが必要か
useState
は直感的で便利ですが、
- 複数の状態が相互に影響する
- 更新ロジックが長い if/else で肥大化する
- 次の状態が前の状態に依存する更新が多い
といった場面では読みづらくなります。こうしたケースでuseReducer
を使うと、状態更新ロジックを1カ所に集約でき、コンポーネントがスッキリします。
基本シグネチャ
const [state, dispatch] = useReducer(reducer, initialState, init?)
パラメータ | 型 | 意味 |
---|---|---|
reducer | (state, action) ⇒ newState | 更新ロジックを持つ純粋関数 |
initialState | 任意 | 初期値 |
init | (optional) (arg) ⇒ initializedState | 遅延初期化用 |
戻り値
state
:現在の状態dispatch
:アクションを送る関数
まずはカウンターで理解
import { useReducer } from 'react';
function reducer(state, action) {
switch (action.type) {
case 'increment': return { count: state.count + 1 };
case 'decrement': return { count: state.count - 1 };
default: throw new Error();
}
}
export default function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>−</button>
</>
);
}
ポイント解説
reducer
は 純粋関数。副作用を入れないdispatch
へは オブジェクトを渡す(type必須)- UI 側は state を読むだけ=ロジックが分離され可読性↑
複数フィールドのフォーム例
useState
でそれぞれ管理すると更新がバラけますが、useReducer
で一元化可能。
function formReducer(state, action) {
switch (action.type) {
case 'change':
return { ...state, [action.field]: action.value };
case 'reset':
return action.initial;
default:
return state;
}
}
const initialForm = { name: '', email: '', password: '' };
function SignupForm() {
const [form, dispatch] = useReducer(formReducer, initialForm);
const handleChange = e =>
dispatch({ type: 'change', field: e.target.name, value: e.target.value });
return (
<form>
{Object.entries(form).map(([key, val]) => (
<input key={key} name={key} value={val} onChange={handleChange} />
))}
<button onClick={() => dispatch({ type: 'reset', initial: initialForm })}>
クリア
</button>
</form>
);
}
深掘り
...state
で既存をコピー=不変性を守るreset
アクションでワンアクション全項目初期化- 大量フィールドでも reducer が変わらないので拡張に強い
遅延初期化でパフォーマンス改善
重い計算で初期値を作る場合、第三引数 init
を使うとレンダー時に毎回呼ばれません。
function init(countFromLocal) {
return { count: countFromLocal };
}
const [state, dispatch] = useReducer(reducer, initialArg, init);
useState vs useReducer 使い分け指針
シチュエーション | 推奨フック |
---|---|
1〜2個の単純な値 | useState |
更新ロジックが直感的 | useState |
多数のフィールド・複雑な分岐 | useReducer |
次の状態が前の状態に強く依存 | useReducer |
Redux 風のアーキテクチャを局所的に実現 | useReducer |
コンポーネント分割との相性
Reducer を別ファイルに切り出し、複数コンポーネントから共通利用するとReduxライクな構成が得られます。
// todoReducer.js
export function todoReducer(state, action) { /* 省略 */ }
// TodoList.jsx
import { todoReducer } from './todoReducer';
const [todos, dispatch] = useReducer(todoReducer, []);
まとめ
useReducer
は状態更新ロジックを一元管理し、可読性と拡張性を両立させる強力なフックです。
- 純粋関数
reducer
を定義 dispatch
で明示的にアクションを送る- ステートが複雑化したら
useReducer
へ移行
ロジックが整理されることでバグ発生率が減り、大規模アプリでもスケールしやすいコードベースを実現できます。
コメント