React Redux の使い方

お仕事で React Redux を使用することになったので、学習した事柄をここにまとめます。


# 学習の指針

React Redux について理解するためには、まず Redux がどのようなものか調べ、それから React Redux に進むのが吉です。

Redux はそれなりにコンテクストヘビーなパッケージなので、React Redux のドキュメントは Redux を理解している前提で書かれているものがほとんどです。なので、一足飛びに React Redux へ進もうとしても、結局 Redux のドキュメントから読み直す羽目になります。

従って、まずは Redux の公式ドキュメントを読むのがよいです。
以下の順に読むのが React Redux に到達する最短経路だと思います。

- [Redux Fundamentals, Part 1: Redux Overview](https://redux.js.org/tutorials/fundamentals/part-1-overview)
- [Redux Fundamentals, Part 2: Concepts and Data Flow](https://redux.js.org/tutorials/fundamentals/part-2-concepts-data-flow)
- [Redux Fundamentals, Part 5: UI and React # Using Redux with React](https://redux.js.org/tutorials/fundamentals/part-5-ui-react#using-redux-with-react)

とはいえ、一時的に React Redux の使い方だけを知りたい方もいると思うので、この記事では React Redux の使い方に絞って内容を記述をします。

具体的には、何を解決するために Redux が必要になるのかを簡単に述べ、その後で React Redux を動作させるための最小限のサンプルを示します。


# React Redux は何を解決してくれるか

React をはじめ、画面をコンポーネントに分割するライブラリを使っていると、しばしば複数のコンポーネントで状態を共有する必要に迫られます。その際に「その状態はどのコンポーネントで管理するべきか？」という微妙な問題が生じます。

状態を共有すべきコンポーネントがごく近い場所にあるうちは、React が提唱する [Lifting State Up](https://reactjs.org/docs/lifting-state-up.html) といった手法を使うことで、親コンポーネントに状態の管理を委譲できます。しかし、アプリケーションの規模が大きくなってくると、それだけでは対応できないケースも出てきます。

遠く離れたコンポーネント間で状態を共有するナイーブな方法は、深い階層の親子関係を辿って状態のバケツリレーを行うことです。しかし、この方法では中間層のコンポーネントが無駄な状態で汚染されるため、可能であれば別の方法を探りたいところです。

Redux は、上記の問題を「コンポーネントツリーの中で状態管理をするのは無理なので、コンポーネントツリーの外で管理しよう」という思想によって解決するためのライブラリです。

Redux では、コンポーネントツリーのどこからでもアクセス可能な、グローバルな空間の中で状態を一元管理します。そして状態が更新された場合、それを検知する必要があるコンポーネントに対して、更新内容をフィードバックします。

一見、グローバル変数を彷彿とさせるアイデアですが、Redux では、状態を管理する空間に適切な構造を持たせることにより、これを容易に管理可能なものとしています。

結論ですが、React Redux を導入する必要があるかどうかは、ひとえに、

- **画面の複数のコンポーネントから参照される複雑な状態があるかどうか**

によって判断するのがよいかと思います。


# Redux の構成

Redux は Action・Reducer・Store・Dispatch・Selector という５つの要素から構成されます。
それぞれの役割は以下の通りです。

- **Action**: 状態更新のトリガーとなるイベントを定義する
- **Reducer**: 各 Action が発行された際の状態の更新方法を定義する
- **Store**: Reducer で定義した更新方法に則って状態を一元管理する
- **Dispatch**: Action を発行する
- **Selector**: Store から状態を取得する

全体の流れは、公式ドキュメントにある [こちらの概念図](https://redux.js.org/assets/images/ReduxDataFlowDiagram-49fa8c3968371d9ef6f2a1486bd40a26.gif) がわかりやすいです。
シンプルな構成なので、あとは実際のコードを読めば大体わかります。

各概念の詳細については、他に説明している記事がたくさんあると思うので、この記事では割愛します。


# サンプルコード

React Redux を動作させる最小限のサンプルコードを示します。
作成するのは React Redux を使用した簡単なカウンタです。

サンプルコードは [Github](https://github.com/dmystk/react-redux-sample) で公開してあるので、とにかく動かしたい方はこちらをどうぞ。

## 準備

サンプルコードは `create-react-app` で作成されるテンプレートを前提とします。

```shell
$ create-react-app react-redux-sample
$ cd react-redux-sample
$ npm install redux react-redux
```

ついでに必要なファイルも作っておきます。

```shell
$ mkdir redux
$ touch redux/action.js
$ touch redux/store.js
```

## Action

Action は `type` プロパティを持つ JavaScript のプレーンオブジェクトです。
`type` はイベントの種類を表すラベルと考えておけば大丈夫です。

以下に Action の定義を示します。
ここではカウンタのインクリメント・デクリメントの Action を定義しています。

```javascript:redux/action.js
export const ActionType = {
  increment: 'increment',
  decrement: 'decrement',
};

export const increment = {
  type: ActionType.increment,
};

export const decrement = {
  type: ActionType.decrement,
};
```

ここでは `type` のみを持つオブジェクトを作成していますが、それ以外のプロパティを持たせることも可能です。追加したプロパティは次に示す Reducer の中で参照可能です。

## Reducer / Store

各 Action に対する状態遷移を定義した Reducer と、それを元に状態を管理する Store を作成します。

このサンプルでは、状態はカウンタの値のみを持つオブジェクトです。
Reducer の中でカウンタの値をインクリメント・デクリメントする状態遷移を表現しています。

```javascript:redux/store.js
import { createStore } from 'redux';
import { ActionType } from './action';

const initialState = {
  count: 0,
};

const reducer = (state = initialState, action) => {
  switch (action.type) {
    case ActionType.increment:
      return { ...state, count: state.count + 1 };
    case ActionType.decrement:
      return { ...state, count: state.count - 1 };
    default:
      return state;
  }
};

const store = createStore(reducer);

export default store;
```

React Redux ではコンポーネントからアクセスする Store を指定するのに Provider というタグを使います。Provider タグの `store` プロパティに作成した Store を渡すことで、全ての子コンポーネントから、引き渡した Store にアクセスできるようになります。

ここでは index.js に App タグを囲う形で Provider タグを追加します。

```jsx:index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

import { Provider } from 'react-redux';
import store from './redux/store';

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
```

## Dispatch / Selector

App.js を書き換えて、Store で管理されているカウンタの値を表示します。また、カウンタの値をインクリメント・デクリメントするためのボタンを追加します。

Store の値を取得するために Selector を使用します。React Redux が提供している `useSelector` フックを使用することで、Store の値が更新された際に、自動的にコンポーネントを更新してくれるようになります。

ボタンの `onClick` には Dispatch を使用して Action を発行するコールバックを登録します。これにより Reducer 経由で Store の状態が更新されます。Dispatch の取得には `useDispatch` フックを使用します。

```jsx:App.js
import './App.css';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './redux/action';

const App = () => {
  const count = useSelector(state => state.count);
  const dispatch = useDispatch();
  return (
    <div className="App">
      <p>{count}</p>
      <button onClick={e => dispatch(increment)}>Increment</button>
      <button onClick={e => dispatch(decrement)}>Decrement</button>
    </div>
  );
}

export default App;
```

以上で React Redux を動作させる最小限のサンプルコードができました。
あとは一連の流れを確認して、煮るなり焼くなりすればよいと思います。


# おまけ

Redux は React の設計思想である Flux を参考に書かれているそうなので、Flux について知っておくと理解が深まるかもしれません。時間がある時に以下のサイトを読んでおくと良さそうです。

- [Github: facebook/flux - flux-concepts](https://github.com/facebook/flux/tree/master/examples/flux-concepts)
- [Prior Art # Flux](https://redux.js.org/understanding/history-and-design/prior-art#flux)