JavaScript

ReactのuseReducerを理解する

React Hooksの1つであるuseReducerとはどんな機能なのかを学んでいきます。

useReducerの使い方

useReducerは、reducerと初期値の2つの引数を取ります

import { useReducer } from 'react';
const [state, dispatch] = useReducer(reducer, initialState);

useReducerは現在の状態を表すstateと、アクションを発行するための関数であるdispatchを配列で返却します。

reducerは2つの引数を受け取り、新しい状態を返す関数です。
引数の1つ目は現在の状態、2つ目はアクションオブジェクトです。

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();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, {count: 0});

  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'DECREMENT'})}>-</button>
      <button onClick={() => dispatch({type: 'INCREMENT'})}>+</button>
    </>
  );
}

上記の例では、INCREMENTとDECREMENTの2つのアクションタイプを持つ単純なカウンターアプリケーションです。

dispatchとreducerの役割

dispatchはreducerにアクションオブジェクトを送る関数で、このアクションオブジェクトに基づいてreducerが新しい状態を生成します。

アクションオブジェクトは一般的にtypeプロパティを持ちますが、それ以外の追加の情報も含めることができます。

一方、reducerは現在の状態とアクションを引数にとり、新しい状態を返す関数です。
そのため、アプリケーションの状態を更新する全てのロジックをここに記述します。

ただし、reducerは純粋関数である必要があるため、実行した結果は引数が同じであれば、必ず結果も同じになる必要があります。
そのため、非同期処理のロジックはreducerの外に記述する必要があります。

まとめ

useStateを使った状態管理は、状態の更新ロジックがコンポーネントの中に閉じ込められてしまうため、コンポーネントが複雑になるにつれて、コードの見通しが悪くなりがちです。

しかし、useReducerを使うことで、状態の更新ロジックをコンポーネントの外に分離できるため、コードの見通しやテスト、コードの再利用が容易になります。

そのため、シンプルな状態管理のロジックのみであればuseState, 複雑な状態管理のロジックではuseReducerを使用するというような使い分けをしたほうが、後々メンテナンスしやすくなりそうです。