🦁

React Docs BETAを読む [状態のロジックをReducerに抽出する]

2022/07/11に公開

React Docs BETAを読む 【状態の管理】
React Docs BETAを読む 【Stateで入力に反応する】
React Docs BETAを読む 【状態構造の選択】
React Docs BETAを読む 【コンポーネント間で状態を共有する】
React Docs BETAを読む 【状態の保持とリセット】
に引き続き状態管理の章を読み進めていきます。
本日は多くの状態更新を伴うコンポーネントの状態更新ロジックをReducerに抽出しようという章です。

Extracting State Logic into a Reducer

多くの状態更新が複数のイベントハンドラーにまたっがているコンポーネントは非常に見づらいですよね。
こういう場合に状態更新を行うロジックを抽出する方法をこの章で学ぶようです。

Consolidate state logic with a reducer

状態更新のロジックをReducerで統合するという節です。
Docsの例にもあるTODOリストを表示するコンポーネントなどは状態が複雑になりぱっと見なにが行われているのか分かりづらくなります。
そういう場合に複雑さをなるべく排除し、ちらばった状態更新のロジックをコンポーネント外の関数に統合することで凝集させアクセスしやすくします。
この外部の関数をReducerと呼ぶようです。
useStateからuseReducerに移行する手順がDocsでは以下の3手順で解説されています。

Step1. Move from setting state to dispatching actions

直訳すると、”状態の設定をディスパッチのアクションに移動する”ですかね。
状態設定ロジックを一旦全て削除し、必要なイベントハンドラだけ残します。
Reducerの場合、アクションをディスパッチすることでユーザが何を行ったのかを指定します。
なのでイベントハンドラの中身をアクションをディスパッチする形に修正していく感じですね。
dispatch関数に渡すアクションオブジェクトは何をしたのかと、付随する情報を渡してあげます。

function handleDeleteTask(taskId) {
  dispatch(
    // "action" object:
    {
      type: 'deleted',
      id: taskId
    }
  );
}

Step 2: Write a reducer function

Reducer関数を書いて行きます。
Reducer関数というのは、状態更新ロジックを記述する場所ですね。
状態とアクションオブジェクトを引数に、次の状態を返却します。
状態更新ロジックからReducer関数に移行するには下の手順を行うと記載があります。

  1. 現在の状態を最初の引数として宣言する。
  2. 2番目の引数にアクションオブジェクトを宣言する。
  3. Reducerから次の状態を返却する。

慣例としてSwitch文を使うようですね。
アクションタイプで分岐することになるので、Switch文のほうが可読性を考えると適切だと思いますね。

Step 3: Use the reducer from your component

最後にReducerを使用したいコンポーネントに宣言を追加します。
useStateからuseReducerに置き換えるだけですかね。
両者は似ているようですが、引数と返り値が微妙に違うので注意です。
Reducer関数自体を別ファイルに書いてあげることで関心を分離させることができます。
コンポーネントはビューに関心を持っていて、状態更新についてはReducer関数側が関心を持っている状態ですね。
こうすることで読みやすくなるようですね。

Comparing useState and useReducer

useStateとuseReducerの特徴についてですね

比較項目 useState useReducer
コードサイズ 小さい 大きい
可読性 複雑にであるほど低くなる 高い
デバッグ 追いづらい 追いやすい
テスト しづらい しやすい

以上でまとめていますが、好みの問題もありますね。

We recommend using a reducer if you often encounter bugs due to incorrect state updates in some component, and want to introduce more structure to its code. You don’t have to use reducers for everything: feel free to mix and match! You can even useState and useReducer in the same component.

上記の通り公式ドキュメントでは状態管理でバグがよく発生し、状態をより構造化したい場合に導入をすすめると記載があります。
また全て統一する必要はないので自由に組み合わせて使用するべきとも記載ありますね。

Writing reducers well

Reducerを書くときのTipsです。

  1. Reducerは純粋でなければならない。(副作用をもってはならない)
  2. 各アクションは複数のデータ更新を伴う場合でも単一のユーザーインタラクションを記述すること。

Writing concise reducers with Immer

Immerを使用してより簡潔にReducerを書く方法のようです。
個人的にImmerを使用したことがないのでわかりませんし、プロジェクトでも導入していないのでなんとも言えないです。
個人プロジェクトには導入してみたいところです。

まとめ

Reducerを使用して状態更新ロジックを抽出することを学びました。
Reduxを使用しているので、この手の書き方はよく見ているので違和感なく使えると思うんですが、このHookを使っていませんでした。
Reduxを使う必要がなく、コンポーネント単位で複雑な状態更新を構造化したい場面はこれまでいくつかあったので、機会があればリファクタリングしてみたいところです。
新しく作成するコンポーネント等ではよく考えつつ導入できればよいなと思います。

GitHubで編集を提案

Discussion