useReducerフック活用法

[Dave Ceddia氏](https://daveceddia.com/about/)による全5回におよぶReact Hooks入門記事の第3回を本人の許可を得て意訳しました。
誤りやより良い表現などがあればご指摘頂けると助かります。

原文： https://daveceddia.com/usereducer-hook-examples/

---

[新機能React Hooks](https://daveceddia.com/intro-to-hooks)の中で、名前だけをみると、これが最もホットです。

「reducer」という言葉は、多くの人にReduxのイメージを思い起こさせますが、この記事を読んだり、React v16.7.0 alphaから使える新しい `useReducer` フックを使うのにReduxを理解する必要はありません。

「reducer」は実際どんなもので、 `useReducer` を使ってコンポーネントの複雑なstateをどう管理するのか、そして、この新しいフックはReduxにとって何を意味するのかについてお話します。Reduxはフックを取り込むのでしょうか？

この記事では、 `useReducer` フックを見ていきます。これは `useState` の時よりも複雑なstateを管理するのに大いに役立ちます。

> メモ：Hooksは現在α版であり、プロダクション環境ではまだ使用できません。APIはさらに変更される可能性があるため、現時点ではプロダクションアプリの書き換えはオススメしません。[Open RFC](https://github.com/reactjs/rfcs/pull/68)にコメントし、[公式ドキュメント](https://reactjs.org/docs/hooks-intro.html)や[FAQ](https://reactjs.org/docs/hooks-faq.html)にも目を通してください。

まずはuseReducerフックでできることを説明した[ビデオチュートリアルをご覧ください](https://youtu.be/sYDFCuZHrqw)。（そのまま読み進んでも構いません）

## Reducerとは？

Reduxもしくは配列の `reduce` メソッドに精通しているのであれば、[「reducer」とは何か？](https://daveceddia.com/what-is-a-reducer/)についてはご存知でしょう。ご存知ない方のために説明しておくと、「reducer」とは、2つの値を取り、1つの値を返す関数を意味する素敵な言葉です。

配列があるとして、この中身を1つの値にまとめたい時、「関数型プログラミング」の手法では、配列の `reduce` 関数を使います。たとえば、数値の配列から合計値を求めたい時は、reducer関数を書いて `reduce` に渡します。こんな感じです。

```javascript
let numbers = [1, 2, 3];
let sum = numbers.reduce((total, number) => {
  return total + number;
}, 0);
```

初見の場合は少し謎めいて見えるかもしれません。これは配列の各要素ごとに関数を呼び出して、前の `total` と現在の要素の `number` を渡します。何を返そうと新しい `total` になります。 `reduce` の第二引数（今回は `0`）は `total` の初期値になります。この例では、reduceに渡される関数（「reducer」関数として知られる）は3回呼ばれます。

- `(0, 1)` で呼ばれて1を返します。
- `(1, 2)` で呼ばれて3を返します。
- `(3, 3)` で呼ばれて6を返します。
- `reduce` は6を返し、 `sum` に格納されます。 

### なるほど、ではuseReducerの役割は？

ページの半分を使って配列の `reduce` 関数を説明してきたのは、 `useReducer` が同じ引数を取り、基本的には同じように動作するからです。reducer関数と初期値（初期state）を渡します。reducerは現在の `state` と `action` を受け取ります。合計値を求めるreducerのようなものを書いてみましょう。

```javascript
useReducer((state, action) => {
  return state + action;
}, 0);
```

それで...何が起こるかって？ `action` はどうやって入ってくるのかって？良い質問ですね。

`useReducer` は2つの要素からなる配列を返します。 `useState` フックと似ています。1番目は現在のstateで、2番目は `dispatch` 関数です。実例で見て行きましょう。

```react
const [sum, dispatch] = useReducer((state, action) => {
  return state + action;
}, 0);
```

「state」がどんな値にもなれることに気を付けてください。オブジェクトである必要はありません。数値、配列などどんな値でも可能です。

このreducerを使って数値をインクリメントするコンポーネントの完全な例を見てみましょう。

```react
import React, { useReducer } from 'react';

function Counter() {
  // 初回のレンダリングでstateが生成されますが、
  // その値は未来のレンダリングにも引き継がれます
  const [sum, dispatch] = useReducer((state, action) => {
    return state + action;
  }, 0);

  return (
    <>
      {sum}

      <button onClick={() => dispatch(1)}>
        Add 1
      </button>
    </>
  );
}
```

[このCodeSandbox](https://codesandbox.io/s/38k7m72yk6)を試してみてください。

ボタンクリックによって、1という値を持った `action` がディスパッチされる流れを確認できます。現在のstateに加算され、コンポーネントは新しい（大きな！）stateで再レンダリングされます。

ここでは敢えて「action」が `{ type: "INCREMENT_BY", value: 1 }` のような形を取らない例を示しました。Hooksで作成するreducerはReduxの典型的なパターンに従う必要がないためです。Hooksの新しい世界では、古いパターンが価値のあるもので、維持されるべきものなのか、変えていくべきものなのかは再考する余地があります。

## より複雑な例

典型的な[Reduxのreducer](https://daveceddia.com/what-is-a-reducer/)により近い例を見ていきましょう。ショッピングリストを管理するコンポーネントを作成します。ここでは新たなフック `useRef` も登場します。

まず最初に、2つのフックをimportする必要があります。

```react
import React, { useReducer, useRef } from 'react';
```

続いて、refとreducerを設定するコンポーネントを作成します。refはフォームの入力項目への参照を保持し、値を抽出することができます。（これまで通り、 `value` と `onChange` propsを使って、stateで入力項目を管理しても構わないのですが、 `useRef` をお披露目する良い機会です！）

```react
function ShoppingList() {
  const inputRef = useRef();
  const [items, dispatch] = useReducer((state, action) => {
    switch (action.type) {
      // アクションに応じて何かをする
    }
  }, []);

  return (
    <>
      <form onSubmit={handleSubmit}>
        <input ref={inputRef} />
      </form>
      <ul>
        {items.map((item, index) => (
          <li key={item.id}>
            {item.name}
          </li>
        ))}
      </ul>
    </>
  );
}
```

今回の「state」が配列であることに留意していください。空配列で初期化し（ `useReducer` の第2引数）、reducer関数から即座に配列を返します。

### useRefフック

少々寄り道してからreducerに戻りますが、ここで `useRef` について説明しておきましょう。

`useRef` フックにより、DOMノードへの永続的な参照を作成することができます。 `useRef` の呼び出しでは、参照のないオブジェクトを作成します。（引数を渡すことで初期値を設定することもできます） `current` プロパティが含まれるため、上記の例では、 `inputRef.current` によって入力項目のDOMノードにアクセスできます。 `React.createRef()` を触ったことがあれば、ほぼ同じように動作すると考えて良いでしょう。

`useRef` によって返されるオブジェクトは、DOMへの参照だけではなく、コンポーネントインスタンス固有の値を持つこともでき、レンダリング間でもその値は保持されます。どこかで聞いたような話ですね！

`useRef` は汎用のインスタンス変数を生成するために使われますが、これはクラスコンポーネントで言うところの `this.whatever = value` と同じようなものです。注意すべき点としては、それが「副作用」として扱われるため、レンダリング時に更新することはできませんが、 `useEffect` フックの中身だけは例外です。（ `useEffect` についての詳細は明日！）[公式のHooks FAQ](https://reactjs.org/docs/hooks-faq.html#is-there-something-like-instance-variables)にはrefをインスタンス変数として活用する例があります。

### useReducerふたたび

入力項目を `form` でラップしたので、エンターキーで送信機能が発火します。 `handleSubmit` 関数でリストに項目を追加できるようにしたり、reducer内のアクションを処理する関数を書いたりする必要があります。

```react
function ShoppingList() {
  const inputRef = useRef();
  const [items, dispatch] = useReducer((state, action) => {
    switch (action.type) {
      case 'add':
        return [
          ...state,
          {
            id: state.length,
            name: action.name
          }
        ];
      default:
        return state;
    }
  }, []);

  function handleSubmit(e) {
    e.preventDefault();
    dispatch({
      type: 'add',
      name: inputRef.current.value
    });
    inputRef.current.value = '';
  }

  return (
    // ... same ...
  );
}
```

reducer関数に2つのケースを追加しました。 actionが `type === 'add'` の時のケースと、それ以外に適用される `default` ケースです。

reducerは「add」actionを受け取ると、全ての古い要素の末尾に新しい要素を追加した新たな配列を返します。

配列の長さを自動インクリメントされるIDとして活用しています。手っ取り早く目的を達成していますが、IDの重複やバグの原因となりやすいため、実際のアプリケーションではおすすめしません。（[uuid](https://www.npmjs.com/package/uuid)のようなライブラリを使うか、サーバー側でユニークなIDを生成するのが良いでしょう！）

`handleSubmit` 関数は、ユーザーが入力項目上でエンターキーを押した時に呼び出されるため、 `preventDefault` でページ全体の更新を抑制する必要があります。ここでactionが `dispatch` されます。このアプリケーションでは、Redux方式のアクション、つまり `type` プロパティと関連データを持つオブジェクトを採用しています。また、入力をクリアしています。

現時点のプロジェクトは[このCodeSandbox](https://codesandbox.io/s/k2vqn76qmr)で確認できます。

### 項目を削除する

続いて、リストから項目を削除する機能を追加してみましょう。

「削除」 `<button>` を項目の隣に追加して、 `type === "remove"` と削除する項目のインデックスを持ったactionをdispatchします。

後は、reducerでアクションを処理するだけです。配列をフィルタリングして不要な項目を削除します。

```react
function ShoppingList() {
  const inputRef = useRef();
  const [items, dispatch] = useReducer((state, action) => {
    switch (action.type) {
      case 'add':
        // ... 前回と同じ ...
      case 'remove':
        // 削除対象以外の項目を保持する
        return state.filter((_, index) => index != action.index);
      default:
        return state;
    }
  }, []);

  function handleSubmit(e) { /*...*/ }

  return (
    <>
      <form onSubmit={handleSubmit}>
        <input ref={inputRef} />
      </form>
      <ul>
        {items.map((item, index) => (
          <li key={item.id}>
            {item.name}
            <button
              onClick={() => dispatch({ type: 'remove', index })}
            >
              X
            </button>
          </li>
        ))}
      </ul>
    </>
  );
}
```

[CodeSandebox](https://codesandbox.io/s/ywmwzq26w9)で確認してみてください。

## 練習：リストをクリアする

もう1つ機能を追加してみましょう。リストをクリアするボタンです。練習あるのみです！

`<ul>` の上にボタンを挿入して、 `onClick` propを設定します。これはtypeが「clear」のactionをdispatchします。次に、reducerにケースを追加して「clear」actionを処理します。

[前のCodeSandboxのチェックポイント](https://codesandbox.io/s/ywmwzq26w9)を開いて更新してみてください（安心してください。私のsandboxは上書きされません）。

...

...

上手く行きましたか？さすがですね！

[答え](https://codesandbox.io/s/vy43p62k83)だけを見ようとしてスクロールしてしまいましたか？このHooksは自分自身で少しでも試してみれば魔法ではないことが理解できるので、ぜひ試してみてください！

## つまり...Redux終了のお知らせ？

多くの人々は最初に `useReducer` フックを見た時にこんなことを考えるでしょう。「なるほど、Reactはビルトインのreducerとデータを引き回すためのContextを獲得したので、Reduxは死んだってことだな！」きっと疑問に感じるでしょうから、私の考えを述べておきます。

個人的に、 `useReducer` は、[ContextがReduxを殺した（ってことはない）](https://daveceddia.com/context-api-vs-redux/)ほどReduxに致命傷与えるものではないと考えています。とはいえ、状態管理におけるReactの機能拡張により、Reduxが本当に必要なケースは減って行くでしょう。

ReduxはContextと `useReducer` の結合以上のことを行います。Redux DevToolsでの素晴らしいデバッギング、カスタム性の高いミドルウェアと、[ヘルパーライブラリのエコシステム全体](https://github.com/markerikson/redux-ecosystem-links)などです。Reduxはやり過ぎとも思えるような場所（[私の例](https://daveceddia.com/how-does-redux-work/)も含めて、その使用法を教えるほとんどすべての例で）で使われることが多いと言っても良いでしょう。それでもまだ強い存在意義があります。

Reduxがグローバルなstoreを提供し、アプリケーションのデータを集中管理するのに対し、 `useReducer` は特定のコンポーネントにローカライズされています。あなた自身のミニReduxを `useReducer` と `useContext` で構築するのを妨げるものはありません！そうしたければ、また必要であれば、やってみてください！（Twitterでは多くの開発者が既にそうして共有したりしています）個人的には、DevToolsがなくて残念ですが、間もなく5〜10もしくは300ものnpmパッケージがそれを解決するために登場するでしょう。

一言で言えば、Reduxは死にません。HooksはReduxを廃れさせることはありません。それは時が経てばはっきりするでしょう。Hooksは数日前に登場し、まだアルファ版です！コミュニティがこの新機能を使って何を作るのかを考えるだけでワクワクします。

## 実際に試してみましょう!

`useReducer` フックを試すのにちょうど良い小さなアプリケーションのアイデアをご紹介します。

- 4つのレベルを持った照明付きの「部屋」を作ってみましょう。オフ、低、中、高のレベルを持ち、ボタンを押すたびに更新されます。
- 正しい順番で押されるとアンロックされる6個のボタンを持つ「キーパッド」を作成してみましょう。正しいボタンを押すたびにstateは更新され、間違ったボタンを押すとリセットされます。