【図解付き】useReducerについて理解する

## はじめに
こんにちは。masakichiです。
今回は、useReducerについて簡単にまとめてみました。

## useReducerの概要理解
useReducerとはstateを用意・管理するためのHooksです。  
useState の代替として使用することもできます。

主に以下3つの登場人物を理解する必要があります。  

![dis.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1006362/55d10766-a436-61c3-0f3e-c11488eff1cd.jpeg)

## useStateとの比較
useStateと比較して、複数の値にまたがる複雑なstate ロジックがある場合などはuseReducerを使います。

また、useReducer を使うと、dispatchを下位コンポーネントに渡せるので、複数階層にまたがって更新を発生させるようなコンポーネントではパフォーマンスの最適化に繋がります。

## useReducerについて

![1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1006362/911d1397-ccbd-e1ec-40c4-cceff9f04a86.jpeg)

useReducerは2つの項目を持つ配列を返します。
1つ目はstateです。stateの初期値は、指定した初期状態に設定されます。
2つ目はstateを更新させるためのdispatch関数です。


:::note info
引数
・reducer関数（stateの更新方法をまとめた関数）
・stateの初期値
:::

:::note info
返り値
・state
・dispatch関数（stateを更新する関数）
:::

## reducer関数について

![2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1006362/7d74268c-f0ee-42b4-cc45-0c8fe0240b29.jpeg)

stateの値を計算して更新するコードを記述した関数です。
慣習的に、switch文として書き、各caseに新しいstateを計算して返します。

:::note info
引数
・更新前のstate
・actionオブジェクト（主にtypeとpayloadプロパティがある）
:::

:::note info
返り値
・reducerで更新された新しいstate
:::

## dispatch関数について
![3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1006362/6fa67c71-2134-3a79-3d9c-ac2ec19b5db1.jpeg)
stateを更新するためのreducer関数を呼び出し、actionと呼ばれるユーザーが行ったことを表すオブジェクトを、reducer関数に渡します。

:::note info
引数
・actionオブジェクト（主にtypeとpayloadプロパティがある）
:::

:::note info
返り値
・void
:::


## useReducerを実装する
それでは実際にuseReducerを使ってコードを書いていきます。  
実装するものカウントアップとダウンができる簡単なものです。  

なおコードは下記サイトのものを参考（ほぼコピペ）にさせていただきました。  

https://www.webopixel.net/javascript/1647.html

### Childコンポーネントを作る
```react.tsx
import "./styles.css";
import { Child } from "./Child";

export default function App() {
  return (
    <div className="App">
      <Child />
    </div>
  );
}
```

```react:Child.tsx
import React from "react";

export const Child: React.FC = () => {
  return (
    <div>
      <h1>カウント:</h1>
      <button>UP</button>
      <button>DOWN</button>
    </div>
  );
};
```

- App.tsxに配置するChildコンポーネントを定義
- 数値を更新するためのボタンを設置しておく

### useReducerを定義する

```react:Child.tsx
import React, { useReducer } from "react";//追記

const initialState = {//追記
  count: 0
};

export const Child: React.FC = () => {
  const [state, dispatch] = useReducer(reducer, initialState);//追記
  return (
    <div>
      <h1>カウント:{state.count}</h1>//追記
      <button>UP</button>
      <button>DOWN</button>
    </div>
  );
};
```

- useReducerを定義する
- useReducerの第二引数にわたす初期値をinitialStateとする
- initialStateのcountプロパティとして0を定義

### reducer関数を定義する
```react:Child.tsx
import React, { useReducer } from "react";

type State = {//追記
  count: number;
};

type Action = { type: "INCREMENT" } | { type: "DECREMENT" };//追記

const reducer = (state: State, action: Action) => {//追記
  switch (action.type) {
    case "INCREMENT":
      return {
        ...state,
        count: state.count + 1
      };

    case "DECREMENT":
      return {
        ...state,
        count: state.count - 1
      };

    default:
      return state;
  }
};

const initialState = {
  count: 0
};

export const Child: React.FC = () => {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <div>
      <h1>カウント:{state.count}</h1>
      <button>UP</button>
      <button>DOWN</button>
    </div>
  );
};
```

- reducer関数を定義し、引数にstateとactionをわたす
- stateとactionの型定義をする
- switch文を記述し、actionオブジェクトのtypeプロパティで分岐させる
- typeプロパティが'INCREMENT'ならcountを1増やす
- typeプロパティが'DECREMENT'ならcountを1減らす
- **stateは読み取り専用。stateのオブジェクトや配列は変更しないこと。**
- 新しく更新したstateをreturnする

### dispatch関数を定義する
```react:Child.tsx
import React, { useReducer } from "react";

type State = {
  count: number;
};

type Action = { type: "INCREMENT" } | { type: "DECREMENT" };

const reducer = (state: State, action: Action) => {
  switch (action.type) {
    case "INCREMENT":
      return {
        ...state,
        count: state.count + 1
      };

    case "DECREMENT":
      return {
        ...state,
        count: state.count - 1
      };

    default:
      return state;
  }
};

const initialState = {
  count: 0
};

export const Child: React.FC = () => {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <div>
      <h1>カウント:{state.count}</h1>
      <button onClick={() => dispatch({ type: "INCREMENT" })}>UP</button>//追記
      <button onClick={() => dispatch({ type: "DECREMENT" })}>DOWN</button>//追記
    </div>
  );
};
```

- ボタンのクリックイベントでdispatch関数を発火させる
- dispatch関数の引数にはactionオブジェクトとしてtypeプロパティを渡す
- typeプロパティはボタンがUPなら'INCREMENT'、DOWNなら'DECREMENT'にする
- dispatch関数がreducer関数を呼び出すので、数値が更新される

## コードリンク先
今回、gifにしたコードは下記リンクにあります。  
https://codesandbox.io/s/usereducer-basic-6us96l


## 参考リンク先
今回の記事を執筆するにあたり下記リンク先を参考にさせて頂きました。  
- [useReducer](https://beta.reactjs.org/apis/usereducer)
- [react-hooks useReducerメモ](https://qiita.com/OmeletteCurry19/items/719c6666940dfe56a7af)
- [Reactの標準機能（useContext/useReducer）でステート管理[TypeScript版]](https://www.webopixel.net/javascript/1647.html)
- [【手書きプログラミング】useReducerとは【React】](https://www.youtube.com/watch?v=iRdoznRFSe8&t=302s)