【React hooks】 useReducer って何？

# はじめに

[フックAPIリファレンス](https://ja.reactjs.org/docs/hooks-reference.html#usereducer)で`useReducer`の存在は知っていましたが、実際に使ったことがなかったので、使い所を調べて使ってみた話をします。

# useReducerとは

https://ja.reactjs.org/docs/hooks-reference.html#usereducer

> `useState`の代用品

役割は、`useState`と大体おんなじ感じってことですかね。

> 現在の state を dispatch メソッドとペアにして返します（もし Redux に馴染みがあれば、これがどう動作するのかはご存じでしょう）。
> useReducer が useState より好ましいのは、複数の値にまたがる複雑な state ロジックがある場合
> 複数階層にまたがって更新を発生させるようなコンポーネントではパフォーマンスの最適化にもなります。

`useState`まみれになったりするの嫌だなぁと思ったことあります。
それを1つのオブジェクトというか`Redux`の`Store`のように、管理できるような感じってことですかね。

# Todoアプリのサンプルを作成

https://github.com/pure-adachi/sample-react-hooks-use-reducer/tree/sample-todo-app

Todoアプリのサンプルを作成しました。

ファイル構成はこんな感じです。

- `src/components/pages/Todos/index.tsx`
    - Todoの追加フォーム、リストデータの表示
- `src/stores/TodosStore/index.ts`
    - `Todos`コンポーネントから`useStore`を呼び出している。
    - `useState`を使って、`loading`と`todos`を管理
    - Todoの追加用と削除用のメソッドを提供

画面の動きはこんな感じです。

バックエンドは用意していないので、それっぽく見せるために、Todoの追加時、削除時に、ダミーの`fetch`を使って、3秒間、ロードが発生するようにしています。

![sample todo app.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/304414/e8236b9f-04f9-beb8-91f7-c7946764f319.gif)

Todo追加時の状態変更の流れは以下の３ステップです。
1. `loading`を`true`に更新
1. `todos`に1件追加更新
1. `loading`を`false`に更新

頭の中のイメージ的には、ステップが1つ少ないです。
1. `loading`を`true`に更新
1. `todos`に1件追加と`loading`を`false`に更新

今回は、`state`が2つなので、それほど複雑ではありませんが、`useReducer`を使って、`Redux`の`Store`のように１つのオブジェクトのように管理してみます。

# useReducerでリファクタリング

`useState`を使っているところを、`useReducer`に変更

```diff:src/stores/TodosStore/index.ts
-const [loading, setLoading] = useState(initialState.loading);
-const [todos, setTodos] = useState(initialState.todos);
+const [state, dispatch] = useReducer(reducer, initialState);
```

`reducer`はこんな感じで定義しておきます。

```ts:reducer
const reducer = (state: TodosStore.State, action: TodosStore.Action) => {
  switch (action.type) {
    case "fetch":
      return fetchAction(state);
    case "add":
      return addAction(state, action);
    case "del":
      return delAction(state, action);
    default:
      return { ...initialState };
  }
};
```

`useReducer`で受けった`dispatch`は、`dispatch({ type: "fetch" });`という感じで呼び出すと、`reducer`関数の`"fetch"`の`case`が実行されるイメージです。

`reducer`は、元の`state`を各処理によって、変更を加えた新しい`state`を返すように記述します。

各処理はこんな感じで定義しておきます。

```ts:fetchAction
const fetchAction = (state: TodosStore.State): TodosStore.State => {
  return { ...state, loading: true };
};
```

```ts:addAction
const addAction = (
  state: TodosStore.State,
  { data: { name } }: TodosStore.AddAction
): TodosStore.State => {
  return {
    ...state,
    loading: false,
    todos: [
      ...state.todos,
      {
        id: new Date().getTime(),
        name,
      },
    ],
  };
};
```

```ts:delAction
const delAction = (
  state: TodosStore.State,
  { data: { id } }: TodosStore.DelAction
): TodosStore.State => {
  const todos = state.todos.filter((todo) => todo.id !== id);

  return {
    ...state,
    loading: false,
    todos,
  };
};
```

`Todos`コンポーネント側から呼び出している、`Todo`追加と削除の関数内で、`dispatch`を使った`state`の更新方法に修正します。

```diff:src/stores/TodosStore/index.ts
+  const dispatchAddTodo = (name: string) => {
+    dispatch({ type: "fetch" });
+
+    return dummyFetch().then(() => dispatch({ type: "add", data: { name } }));
+  };
+
+  const dispatchDelTodo = (id: number) => {
+    dispatch({ type: "fetch" });
+
+    return dummyFetch().then(() => dispatch({ type: "del", data: { id } }));
+  };

   const store: TodosStore.Store = {
     state,
     dispatchAddTodo,
     dispatchDelTodo,
   };

   return store;
 }
```

https://github.com/pure-adachi/sample-react-hooks-use-reducer/compare/sample-todo-app...add-use-reducer

動きに変化はありませんが、頭の中のイメージ通りのステップ数になったので、レンダリング回数が減りました。

というわけで、リファクタリング完了！

# おわりに

ベストプラクティスは分かっていませんが、自分なりに`Redux`を意識して、`Store`、`Reducer`、`Action`用にファイルを分けて、簡潔に実装できたのではないかと思います、

今回は、１ファイルに全ての機能を実装していますが、実際は、ファイルが分かれたりして、でも同じ`state`を参照した状態でアクションを実行したいことがあると思います。

その時は、`useContext`を使えば、各ファイルから同じ`state`を参照できるような仕組みにもできると思います。（`props`で頑張って渡す方法でも...）

`state`に直接変更を加えるわけではなく、`Store`側でアクションを定義して、そちら側で`state`を変更する仕組みの方が、安全性があると思いますし、何より、たくさん管理したい状態がある程、1箇所にまとまっていて、そこにアクションを追加していく方が、分かりやすいのかなと思いました。

# 参考文献

- [React hooksを基礎から理解する (useReducer編) - Qiita](https://qiita.com/seira/items/2fbad56e84bda885c84c)