Kodokon kodokon.com

複雑な state のための useReducer

遷移を一元化し、あり得ない組み合わせを排除する純粋なリデューサーで、複雑な state を構造化します。

9 分 · 3 問

このレッスンを Kodokon で開く

コンポーネントが一緒に変化する複数の state 値 - リクエストのステータス、データ、エラーなど - を扱うようになると、useState の呼び出しを増やすたびに遷移ロジックがすべてのイベントハンドラーに散らばってしまいます。症状はご存じのとおりです。正しい順序で呼ぶべき 3 つのセッター、そして遅かれ早かれ生まれる矛盾した state、たとえば success というステータスの隣に null でないエラーが並ぶような状態です。useReducer はこの責務を逆転させます。state は 1 つのオブジェクトになり、あらゆる変更は 1 つの中心的な関数、すなわちリデューサーが解釈する アクション を経由します。

JAVASCRIPT
function requestReducer(state, action) {
  switch (action.type) {
    case "start":
      return { status: "loading", data: null, error: null };
    case "resolve":
      return {
        status: "success",
        data: action.data,
        error: null,
      };
    case "reject":
      return {
        status: "error",
        data: null,
        error: action.error,
      };
    default:
      throw new Error("Unknown action: " + action.type);
  }
}
各遷移が完全で一貫した state を再構築します。

リデューサーは純粋関数です。現在の state とアクションを受け取り、副作用なしで次の state を返します。利点はすぐに現れます。遷移が 明示的かつ網羅的 になり、あり得ない state は根本から消え(各 case が一貫したオブジェクトを再構築します)、ロジックがあちこちに散らばる代わりに 1 か所で読めるようになります。有用な警告サインがあります。同じ setState 呼び出しの連なりを 2 か所にコピー&ペーストしているなら、おそらくリデューサーが足りていません。

JSX
function UserProfile() {
  const [state, dispatch] = useReducer(requestReducer, {
    status: "idle",
    data: null,
    error: null,
  });

  async function load(id) {
    dispatch({ type: "start" });
    try {
      const res = await fetch("/api/users/" + id);
      const data = await res.json();
      dispatch({ type: "resolve", data });
    } catch (error) {
      dispatch({ type: "reject", error });
    }
  }

  return <button onClick={() => load(1)}>Load</button>;
}
コンポーネントは意図を表現し、リデューサーが state を決定します。

useStateuseReducer のどちらを選ぶかは好みの問題ではありません。単純で独立した値には useState を使い、次の state が前の state に依存する場合や複数のフィールドが一緒に変化する場合は useReducer に切り替えます。さらに 2 つの利点があります。第 3 引数は 遅延初期化 を提供し(useReducer(reducer, arg, init) は最初のレンダー時にのみ init を実行します)、React は dispatch がレンダー間で同じ識別子を保つことを保証します。そのため useCallback なしで子に渡せます。

理解度チェック

このレッスンの要点をしっかり覚えているか確認しましょう。

  1. 複数の useState 呼び出しではなく useReducer へ導くべきサインはどれですか?
    • コンポーネントが 100 行を超えている
    • 複数の state 値が一緒に変化し、新しい state が前の state に依存している
    • コンポーネントが 3 つを超える props を受け取っている
    • state が配列を含んでいる
  2. リデューサーが受け取った state オブジェクトをその場で変更して返しました。React はどうしますか?
    • 新旧の参照を比較し、等しいと判断して再レンダーをスキップする
    • 深いレベルの変更を検出してコンポーネントを再レンダーする
    • 開発時には state がフリーズされているためエラーを投げる
  3. なぜ特別な用心なしにメモ化された子へ dispatch を渡せるのですか?
    • React.memo は関数型の props を無視するから
    • dispatch は再生成されるが、レンダーごとに構造的に等しいから
    • React は dispatch がコンポーネントの生存期間全体を通じて同じ識別子を保つことを保証するから