useReducer 完全入門【React】複雑な状態管理をスッキリ整理する

ReactのuseReducerとは何か、reducer関数とdispatchの使い方を初心者向けに解説。useStateと使い分けるべき場面や、複数の状態をまとめて管理するパターンを紹介します。

#react#hooks#usereducer#javascript#frontend

useState を使っていると、状態が増えるにつれてコンポーネントが複雑になっていきませんか?「追加」「削除」「編集」「完了」など、複数のアクションがある場合、更新ロジックがどんどん増えて管理が難しくなります。

そんなときに役立つのが useReducer です。状態の更新ロジックを「reducer(リデューサー)関数」としてまとめることで、コードが整理しやすくなります。

この記事でわかること:

  • useReducer の仕組みと基本的な使い方
  • reducer 関数と dispatch の書き方
  • useState との使い分けの基準
  • ToDoアプリを例にした実践的な実装

useReducer とは?

useReducer は、状態の更新ロジックをコンポーネントの外に切り出すフックです。

const [state, dispatch] = useReducer(reducer, initialArg, init?);
パラメータ説明
reducer状態更新のロジックを定義した関数
initialArg初期 state の値
init(省略可能)初期化関数(指定するとinit(initialArg)の結果が初期値になる)
戻り値説明
state現在の state
dispatchアクションを発行して state を更新する関数

reducer 関数の書き方

reducer 関数は (現在のstate, action) => 次のstate という形の純粋関数です。

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(`Unknown action: ${action.type}`);
  }
}

重要state を直接書き換えてはいけません。必ず新しいオブジェクトを返します。

基本的な使い方

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:
      return state;
  }
}
 
function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });
 
  return (
    <>
      <p>カウント: {state.count}</p>
      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
    </>
  );
}

dispatch にはアクションオブジェクトを渡します。慣例として type プロパティで何をするかを指定し、追加データがあれば一緒に渡します。

実践例:ToDoアプリ

import { useReducer } from "react";
 
// reducer 関数(コンポーネントの外に定義する)
function todoReducer(todos, action) {
  switch (action.type) {
    case "added":
      return [
        ...todos,
        { id: Date.now(), text: action.text, done: false },
      ];
    case "toggled":
      return todos.map((todo) =>
        todo.id === action.id ? { ...todo, done: !todo.done } : todo
      );
    case "deleted":
      return todos.filter((todo) => todo.id !== action.id);
    default:
      throw new Error(`Unknown action: ${action.type}`);
  }
}
 
function TodoApp() {
  const [todos, dispatch] = useReducer(todoReducer, []);
  const [text, setText] = useState("");
 
  const handleAdd = () => {
    if (!text.trim()) return;
    dispatch({ type: "added", text }); // アクションを発行
    setText("");
  };
 
  return (
    <div>
      <input
        value={text}
        onChange={(e) => setText(e.target.value)}
        placeholder="タスクを入力"
      />
      <button onClick={handleAdd}>追加</button>
 
      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>
            <input
              type="checkbox"
              checked={todo.done}
              onChange={() => dispatch({ type: "toggled", id: todo.id })}
            />
            <span style={{ textDecoration: todo.done ? "line-through" : "" }}>
              {todo.text}
            </span>
            <button onClick={() => dispatch({ type: "deleted", id: todo.id })}>
              削除
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

useState vs useReducer の使い分け

状況使うフック
単一の値(数値・文字列・真偽値)useState
独立した複数の値useState(複数回)
関連する複数の値をまとめて更新useReducer
複数のアクションによる複雑な更新ロジックuseReducer
次の state が前の state に依存するuseReducer

目安として:

  • 3つ以下の独立した stateuseState がシンプル
  • 同じイベントで複数の state が変わるuseReducer を検討
  • 更新ロジックが複雑でテストしたいuseReducer(reducer 関数は純粋関数なので単体テストしやすい)

初期化関数を使う

計算コストが高い初期値の場合、第3引数に初期化関数を渡せます。

function createInitialState(initialCount) {
  return { count: initialCount, history: [] };
}
 
// ✅ 初回レンダリングのときだけ createInitialState が実行される
const [state, dispatch] = useReducer(reducer, 0, createInitialState);
// → { count: 0, history: [] } が初期値になる

まとめ

  • useReducer は状態の更新ロジックを reducer 関数にまとめるフック
  • dispatch({ type: "actionType", ...payload }) でアクションを発行する
  • reducer 関数は純粋関数で、常に新しい state オブジェクトを返す
  • 関連する複数の値・複雑な更新ロジックがある場合に useState より適している
  • reducer 関数をコンポーネントの外に定義することで、ロジックが整理しやすくなる

PR

useReducer 公式ドキュメントで詳細を確認しよう

useReducerのインタラクティブなサンプルや、ImmerとuseReducerを組み合わせたパターンは公式ドキュメントで確認できます。

useReducer 公式ドキュメントを見る

関連記事