useReducerについて理解を深める

## useReducerについて
`useReducer`はReactで`state`を扱うために用意されたhooksのひとつです。
`reducer`と呼ばれる`state`の更新ロジックを集約した関数を用いて`state`を操作します。これにより、複数の方法・複数の箇所で更新を行うような場合はコード全体の見通しが非常によくなります。
その他にも`reducer`として外部の関数に更新処理を書いていることによって、テストやデバックを行いやすいという特徴を持ちます。

`useReducer`は`reducer`を使って以下のように利用します。

```tsx
const [state, dispatch] = useReducer(reducer, initializerArg, initializer);
```
`initializerArg`は`state`の初期値の元となる値です。`initializer`は`initializerArg`を引数に取り初期値を生成する関数です。`initializer`は省略可能で省略した場合はそのまま`initializerArg`が初期値になります。第三引数を渡さずに第二引数に`state`の初期値を`initializer(initializerArg)`を渡すようにしない理由は`initializer`を初期レンダリング時でのみ実行させるためです。

例として`state`の型が`number`で`+1`または、`-1`しか更新しないような場合を考えます。`useState`を用いた場合は以下のように`state`から提供されるセッターをもとに`increment`関数と`decrement`関数を用意して`state`の更新を行います。

```tsx
const [count, setCount] = useState(0);
const increment = () => setCount((count) => count + 1);
const decrement = () => setCount((count) => count + 1);
```

この方法では`state`を更新する関数がさまざまな場所で宣言されて散らかったり、セッターをそのまま用いた自由な更新が行われる余地が残されるような問題があります。
これらの問題を解決するのが`useReducer`です。
```tsx
const countReducer(
  count: number,
  action: { type: 'increment' | 'decrement' },
): number {
  switch (action.type) {
    case 'increment': {
      return count + 1;
    }
    case 'decrement': {
      return count - 1;
    }
  }
}

const [count, dispatch] = useReducer(countReducer, 0);
// incrementは`dispatch({ type: 'increment' });`
// decrementは`dispatch({ type: 'decrement' });`
```
`reducer`として`state`の更新処理を記述した関数で`state`の更新を行うので更新方法の定義が散らかる問題と自由な更新の両者の問題を解決できました。
しかし、`useState`よりも`useReducer`の方が優れているわけではありません。先ほどの例では確かに提示した問題点を解決する点では優れて見えますが、コード量は膨れていますし、私には可読性も`useState`の方に軍牌が上ると考えています。
`useState`と`useReducer`のコード量や可読性は`state`をどのように扱うかによって優位性は変わります。より複雑で複数の方法で更新される`state`は`useReduer`の方が有利だったりします。状況に応じて使い分けるようにしましょう。

### `reducer`関数
`useReducer`が提供する`state`の更新方法を示す関数である`reducer`にはいくつかの慣習とポイントがあります。
慣習としては更新方法の指定は`type`を元に行うということです。

```tsx
const countReducer(
  count: number,
  action: { type: 'increment' | 'decrement' },
): number {
  switch (action.type) {
    case 'increment': {
      return count + 1;
    }
    case 'decrement': {
      return count - 1;
    }
  }
}
```
`type`を通して文字列で更新方法を指定して分岐させるように定義することが慣習とされています。
そして、気をつけておくポイントが２つあります。`reducer`が純粋であることと操作単位で記述することです。
純粋性はReactを書く上で多くの場所で出てきます。`reducer`は同じ`action`を渡したら必ず同じ結果を返すような関数にしてください。呼び出しの順番やタイミングなどで結果が変わるような関数は書かないようにしましょう。
そして操作単位で更新方法を指定することです。
```ts
type User = {
  id: number;
  givenName: string;
  familyName: string;
  mailaddress: string;
};
```
このように定義された型を更新する`reducer`を記述する場合は`type`を`set_id`や`set_given_name`のように状態を更新するための単位ではなく、`init`や`update_name`のように操作単位にしましょう。これによって`state`を更新する際に必要な複数の`state`の更新処理を呼び出すのではなく、状況に合わせて１つの更新処理を呼び出すだけで済みます。これによって予期せぬバグを防ぐこともできますし、デバックも容易になり可読性も向上します。
```tsx
// stateのフィルードごとの定義
<button
  onClick={() => {
    dispatch('set_id', '878fa115-4310-4bf2-b0c8-006da6073332');
    dispatch('set_given_name', '');
    dispatch('set_family_name', '');
    dispatch('set_mailaddress', '');
  }}
>
  初期化
</button>

// 操作単位の定義
<button
  onClick={() => dispatch('init')}
>
  初期化
</button>
```

### 型
`useReducer`の型は以下のように定義されています。
```ts
type DispatchWithoutAction = () => void;
type Reducer<S, A> = (prevState: S, action: A) => S;
type ReducerWithoutAction<S> = (prevState: S) => S;
type ReducerState<R extends Reducer<any, any>> = R extends Reducer<infer S, any> ? S : never;
type ReducerAction<R extends Reducer<any, any>> = R extends Reducer<any, infer A> ? A : never;
type ReducerStateWithoutAction<R extends ReducerWithoutAction<any>> =
    R extends ReducerWithoutAction<infer S> ? S : never;

function useReducer<R extends ReducerWithoutAction<any>, I>(
    reducer: R,
    initializerArg: I,
    initializer: (arg: I) => ReducerStateWithoutAction<R>
): [ReducerStateWithoutAction<R>, DispatchWithoutAction];

function useReducer<R extends ReducerWithoutAction<any>>(
    reducer: R,
    initializerArg: ReducerStateWithoutAction<R>,
    initializer?: undefined
): [ReducerStateWithoutAction<R>, DispatchWithoutAction];

function useReducer<R extends Reducer<any, any>, I>(
    reducer: R,
    initializerArg: I,
    initializer: (arg: I) => ReducerState<R>
): [ReducerState<R>, Dispatch<ReducerAction<R>>];

function useReducer<R extends Reducer<any, any>>(
    reducer: R,
    initialState: ReducerState<R>,
    initializer?: undefined
): [ReducerState<R>, Dispatch<ReducerAction<R>>];
```
まとめてみると仰々しいのでひとつずつピックアップして見ていきます。
#### 1個目
```ts
type DispatchWithoutAction = () => void;
type ReducerWithoutAction<S> = (prevState: S) => S;
type ReducerStateWithoutAction<R extends ReducerWithoutAction<any>> =
    R extends ReducerWithoutAction<infer S> ? S : never;

function useReducer<R extends ReducerWithoutAction<any>, I>(
    reducer: R,
    initializerArg: I,
    initializer: (arg: I) => ReducerStateWithoutAction<R>
): [ReducerStateWithoutAction<R>, DispatchWithoutAction];
```
これは`initializer`が与えられ、`dispatch`に引数を持たせない場合の`useReducer`です。
```ts
const [state, dispatch] = useReducer(
  (state) => console.log(state),
  'hello',
  (state) => `${state} world`,
);
```
`reducer`は受け取った引数の型を返り値にも持つ関数です。`initializerArg`は適当な値です。`initializer`は`initializerArg`に渡した値の型を引数にとって、`reducer`の引数や返り値の型を返り値に持つ関数です。
返り値は`reducer`の引数や返り値の型と、引数がなしで返り値が`void`の関数を配列に詰めて返します。
#### 2個目
```ts
type Dispatch<A> = (value: A) => void;
type DispatchWithoutAction = () => void;
type ReducerWithoutAction<S> = (prevState: S) => S;
type ReducerStateWithoutAction<R extends ReducerWithoutAction<any>> =
    R extends ReducerWithoutAction<infer S> ? S : never;

function useReducer<R extends ReducerWithoutAction<any>>(
    reducer: R,
    initializerArg: ReducerStateWithoutAction<R>,
    initializer?: undefined
): [ReducerStateWithoutAction<R>, DispatchWithoutAction];
```
これは`initializer`が与えられず、`dispatch`に引数を持たせない場合の`useReducer`です。
```ts
const [state, dispatch] = useReducer(
  (state) => console.log(state),
  'hello world',
);
```
`reducer`は受け取った引数の型を返り値にも持つ関数です。`initializerArg`は`reducer`の引数や返り値の型です。`initializer`は渡さないので`undefined`です。
返り値は`reducer`の引数や返り値の型と、引数がなしで返り値が`void`の関数を配列に詰めて返します。

#### 3個目
```ts
type Dispatch<A> = (value: A) => void;
type ReducerState<R extends Reducer<any, any>> = R extends Reducer<infer S, any> ? S : never;
type ReducerAction<R extends Reducer<any, any>> = R extends Reducer<any, infer A> ? A : never;
type Reducer<S, A> = (prevState: S, action: A) => S;

function useReducer<R extends Reducer<any, any>, I>(
    reducer: R,
    initializerArg: I,
    initializer: (arg: I) => ReducerState<R>
): [ReducerState<R>, Dispatch<ReducerAction<R>>];
```
これは`initializer`が与えられ、`dispatch`が引数を持つ場合の`useReducer`です。
```ts
const [state, dispatch] = useReducer(
  (state, actions) => reducer(state, action),
  'hello',
  (state) => `${state} world`,
);
```
`reducer`は受け取った第一引数の型を返り値にも持ち、`state`の更新に関する情報を第二引数にもつ関数です。`initializerArg`は適当な値です。`initializer`は`initializerArg`に渡した値の型を引数にとって、`reducer`の第一引数や返り値の型を返り値に持つ関数です。
返り値は`reducer`の第一引数や返り値の型と、引数が`reducer`の第二引数の型で返り値が`void`の関数を配列に詰めて返します。
#### 4個目

```ts
type Dispatch<A> = (value: A) => void;
type ReducerState<R extends Reducer<any, any>> = R extends Reducer<infer S, any> ? S : never;
type ReducerAction<R extends Reducer<any, any>> = R extends Reducer<any, infer A> ? A : never;
type Reducer<S, A> = (prevState: S, action: A) => S

function useReducer<R extends Reducer<any, any>>(
    reducer: R,
    initialState: ReducerState<R>,
    initializer?: undefined
): [ReducerState<R>, Dispatch<ReducerAction<R>>];
```
これは`initializer`が与えられず、`dispatch`が引数を持つ場合の`useReducer`です。
```ts
const [state, dispatch] = useReducer(
  (state, actions) => reducer(state, action),
  'hello world',
);
```
`reducer`は受け取った第一引数の型を返り値にも持ち、`state`の更新に関する情報を第二引数にもつ関数です。`initializerArg`は適当な値です。`initializer`は`reducer`の第一引数や返り値の型です。
返り値は`reducer`の第一引数や返り値の型と、引数が`reducer`の第二引数の型で返り値が`void`の関数を配列に詰めて返します。


## useStateからuseReducerを作る
`useState`から`useReducer`を作成することで理解を深めていきます。既存の名前と被らないように`useCustomReducer`と名付けて実装していきます。

型については先ほど見た型を付与すれば良いだけなので端折ります。そのため、型を考えずにJavaScriptで実装していきます。
```js
function useCustomReducer(
  reducer,
  initializerArg,
  initializer,
) {
  const [state, setState] = useState(
    () => {
      if (initializer === undefined) {
        return initializerArg;
      }
      return initializer(initializerArg);
    };
  );

  const dispatch = useCallback((action) => {
    setState((state) => reducer(state, action));
  }, [reducer]);

  return [state, dispatch];
}
```
第3引数の`initializer`はデフォルトで自身の値を返すような関数となるようにしました。指定しないときは`initializeArg`が初期値になるようにするためです。
そして`useState`を呼び出してベースとなる`state`を生成します。
`useReducer`の初期値を生成するために引数を第2引数と第3引数に分離している理由は初期レンダリング以外での計算の省略のためでした。そのためここで呼び出した`useState`の初期値も`initializer(initializerArg)`を直接与えるのではなく、`() => initializer(initializerArg)`のように渡すことで初期レンダリング以降での計算を省略するようにしました。
そして、`dispatch`では`setState`内で取得した`state`を`reducer`に渡して既存の値を更新するようにしました。これは、`useReducer`の更新はスナップショットに対する操作ではなく、レンダリング時に`state`を参照して更新を行うからです。

https://codesandbox.io/embed/usereducer-gd8t8d?fontsize=14&hidenavigation=1&theme=dark

これでuseReducerと同等の機能を持つ`useCustomReducer`(型なし)ができました。

https://codesandbox.io/embed/usecustomreducer-d7t9mw?fontsize=14&hidenavigation=1&theme=dark