Reduxによる状態管理の仕組みを理解しよう

#目次

* [概要](#概要)
* [Reduxとは](#reduxとは)
* [Reduxの要素](#reduxの要素)
* [Reduxのデータフロー](#reduxのデータフロー)
* [Action](#action)
* [Reducer](#reducer)
* [Store](#store)
* [connect関数](#connect関数)
* [Provider](#provider)
* [まとめ](#まとめ)

#概要
この記事では、状態管理を行うためのフレームワークであるReduxの基礎や状態管理の仕組みについてまとめています。現在Reduxについて勉強中の方の参考になるようでしたら幸いです。
なお本記事はReactを用いていることを前提条件としています。

(注意)
またReact ComponentとRedux Storeを関連づける手法として、現在はHooksとReduxを用いた手法もありますが、今回は従来のconnect関数を用いた手法で紹介しています。後日Hooksを用いた手法についても投稿予定です。


#Reduxとは
Reduxとは、上記でも述べたようにReactの**状態(state)を管理するフレームワーク**です。
またReduxはReactと併用することを想定して生み出されているため、Reactと非常に相性が良いとされています。

**ReduxはFluxアーキテクチャの一つ**で、コンポーネントの数が多くなったときに簡単にstateを共有するための手段として利用されています。

状態(state)の共有イメージ
![redux.001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/622836/d572feab-947f-a285-03c6-8557db154855.png)


またReduxなどのFluxアーキテクチャの最大の特長は**データフローが単方向で構築できる**ことで、規模が大きくなった場合にもデータの流れを見失いにくくなります。(後ほど図解)

Reduxを使用するためには以下のコマンドでインストールしておく必要があります。

```bash:
$ yarn add redux react-redux
```


#Reduxの要素
Reduxによる状態管理を行うための構成要素には主に以下の４つが必要となります。

* **Action**：アプリケーション内でなにが起きたのかを示す**オブジェクト(データ)**
* **Reducer**：Actionのtype(種類)に応じてstateを変化させる**メソッド**
* **Store**：アプリケーション内の全てのstateを保持している**場所**
* **State**：アプリケーションの**状態**


#Reduxのデータフロー
上記のような構成にすることで、先ほど述べた**データフローの単方向化を実現**することが可能にすることができます。
![redux.002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/622836/0fe2ddff-221e-efb1-2924-e2a7df25e891.png)

1. ActionCreatorによってActionを生成
2. Actionをdispatch
3. ReducerでStore内のStateを更新
4. Store内のStateをReact Componentで参照(ReactとReduxの連携)


では次にAction、Reducer、Store、ReactとReduxの連携方法について詳しく説明します。
（stateについては省略します）

#Action
Actionの特徴は以下のことが挙げられます。

* アプリケーションの中でなにが起きたかを示すオブジェクト（データ）である
* `type`とそれに対応する値を持つ（typeの値はユニークなものにする）
*  Storeの唯一の情報源
*  ActionCreatorによって生成される

###**ActionCreator**
**Actionを作成するメソッド**
FluxにもActionCreatorがありますが、ReduxではActionを作成するのみで**Storeへのdispatchは行わない**という違いがあります。

例えばToDoリストアプリケーションでリストを追加したいときにはActionCreatorと組み合わせて次のように記述します。

```react

const ADD_TODO = 'ADD_TODO';

// Action Creator
const addTodo = text => {
  return {
    // Action
    type: ADD_TODO,
    text
  }
}
```
前述の通り、ActionCreatorによって生成されたActionは、生成されたのみでStoreへdispatchされていません。
Reduxでは`dispatch()`メソッドによってActionをStoreに送ることができます。

```react
dispatch(addTodo(text));
```

#Reducer
Reducerの特徴は以下のことが挙げられます。

* Actionの`type`に応じて状態をどう変化させるのかを定義したメソッドである
* 引数のstateを更新するのではなく、**新しいStateを作成**して返す
* 純粋関数でないとならない(毎回必ず同じ結果を返す)

Reducerの実装手順は以下のようになります。

1. 状態(state)はオブジェクトとして初期値を定義
2. Reducerは関数として定義、引数は２つ(state, action)
3. Actionのtypeに応じて状態を変化させ結果を返す

コードで見ると以下のようなイメージになります。

```react:src/reducers/reducer1
import { ADD_TODO, REMOVE_TODO } from '../actions';

const initialState = { text: 'initial text' };

export default (state = initialState, action) => {
  switch(action.type) {
    case ADD_TODO:
      return {
        // ActionがADD_TODOの場合のState更新処理
      };
    case REMOVE_ADD:
      return {
        // ActionがREMOVE_TODOの場合のState更新処理
      };
    default:
      return state;  // Actionのtypeに当てはまらない場合はデフォルトを返す
  }
};
```
ToDoリストとして正しいinitialStateの設定かは分かりませんが、Reducerの実装イメージとしてはこんな感じになります。

またReduxではアプリケーション内に存在する**全てのReducerを1つのReducerとして結合**することできます。

```react:src/reducers/index.js
import { combineReducers } from 'redux';  // このimportが必要
import reducer1 from './reducer1';
import reducer2 from './reducer2';

export default combineReducers({ reducer1, reducer2 });
```


#Store
Storeの特徴は以下のことが挙げられます。

* Storeはアプリケーション内で唯一のもの
* アプリケーション内のStateが全て集約されている
* 従来のReactではpropsを目的のコンポーネントまでバケツリレー形式で渡していたが、`<Provider>`によりその必要がなくなる(react-reduxからインポート)

Storeは以下のように作成します。

```react:src/index.js
import {createStore} from 'redux';
import reducer from './reducers';

const store = createStore(reducer);
```


#connect関数
ReactとReduxは互いに独立しているため、なにもしないままだとReact ComponentをReduxのフローに乗せることができません。そこでReact Reduxが提供している`connect()`関数を利用して、**ReduxのStateやActionとReact Componentを接続**します。このとき接続されたComponentは**Container**と呼ばれることもあります。

このように`connect()`関数によってContainerではStoreから必要なデータ(Stateの一部)と、ActionをStoreに渡すための`dispatch()`関数を使用することができます。
基本的な記述方法としては以下のように書きます。

```react
connect(mapStateToProps, mapDispatchToProps)(App);
```
ここで`connect()`関数の引数に渡されている`mapStateToProps`と`mapDispatchToProps`について詳しく見ていきましょう。

###mapStateToProps
**StoreにあるStateの情報からContainerで必要な情報を取り出し**、Container内のpropsとしてマッピングする機能を持つ関数です。`mapState`として呼ばれることもあります。
上記でも示したように`connect()`関数の第1引数になります。

|                 |                                        |
|:---------------:|:---------------------------------------|
|**呼ばれるタイミング**|Storeの状態が変化したとき                   |
|**第1引数**       |State(Storeの全ての状態)                  |
|  第2引数(オプション)|自身のprops(ownProps)                    |
|**戻り値**         |Containerが必要としているStateをpropsとして返す|

```react
const mapStateToProps = state => {
  return {
    // 全体のStateから必要な情報 (state.valueなど)
    // value: state.value
    // のように記述
  };
};
```
ownPropsを第２引数に設定した場合、最終的な戻り値にはownPropsと新たに得たデータが１つのpropsとしてマージされます。


###mapDispatchToProps
**ActionをStoreにdispatchする**`dispatch()`関数をpropsにマッピングするために使用されます。`mapDispatch`として呼ばれることもあります。
`dispatch()`関数とは、あるActionが発生したときにReducerに`type`に応じた状態遷移を実行させるための関数です。Storeに標準で`dispatch()`関数が用意されています。

`mapDispatchToProps`を`connect()`関数の引数に指定しなくてもコンポーネントはデフォルトで`props.dispatch`を受け取れるので、これを使用してStoreにActionをdispatchすることができます。

では`mapDispatchToProps`を使用する理由はなんでしょうか？
例として、「ボタンを押すと`value`が+1される」というContainerを見てみましょう。

**`mapDispatchToProps`を使用しない場合**
Container側でActionCreatorによってActionを作成した段階ではdispatchされていないので以下のように記述する必要があります。

```react
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { increment } from '../actions';

class Counter extends Component {
  render(){
    const props = this.props;
    return (
      <React.Fragment>
        <div>count: {props.value}</div>
        <button onClick={() => dispatch(increment())}>+1</button>  {// ここ }
      </React.Fragment>
    );
  }
}

const mapStateToProps = state => ({ value: state.count.value});
export default connect(mapStateToProps /*, 第２引数なし（mapDispatchToProps） */)(Counter);
```
若干JSXの記述が複雑になっています。

**`mapDispatchToProps`を使用した場合**

```react
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { increment } from '../actions';

class Counter extends Component {
  render(){
    const props = this.props;
    return (
      <React.Fragment>
        <div>value: {props.value}</div>
        <button onClick={props.increment}>+1</button>
      </React.Fragment>
    );
  }
}

const mapStateToProps = state => ({ value: state.count.value});
const mapDispatchToProps = ({ increment });
// 上記は下の記述と同じ意味
// const mapDispatchToProps = dispatch => ({
//   increment: () => dispatch(increment())
// });

export default connect(mapStateToProps, mapDispatchToProps)(App);
```
このように記述することができます。
`mapDispatchToProps`内では**ActionCreatorでActionを生成しdispatchまで行う処理を関数に定義してpropsに渡しています**。

今回は処理が少ない場合でしたのであまり影響はありませんが、Actionの種類や作成の数が多くなった場合には`mapDispatchToProps`を使用することでContainer側で毎回`dispatch`処理を記述する必要がなくなります。

|                 |                                        |
|:---------------:|:---------------------------------------|
|**呼ばれるタイミング**|ActionCreatorが呼び出されたとき<br>(今回はクリックされたとき)|
|**第1引数**       |dispatch()関数                           |
|  第2引数(オプション)|自身のprops(ownProps)                    |
|**戻り値**         |指定したActionのdispatch処理をpropsとして返す |

第2引数としてownPropsを設定した場合、Containerが新しいpropsを受け取ったタイミングで`mapDispatchProps`が呼び出されます。


#Provider
Component階層の最上位のComponentを`<Provider>`でネストすることで**全体のComponentをStoreに接続することが可能**になります。
それによって任意のComponentで`connect()`関数を用いてStoreと接続できます。

コードの記述イメージは以下のようになります。

```react:src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';

import { App } from './App';
import createStore from './createReduxStore';

const store = createStore();

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


#まとめ
長くなりましたが、Reduxの基礎については以上にしたいと思います。
では簡単に今回の記事の内容をまとめます。

* **Redux**:アプリケーションの状態管理を行うためのフレームワーク
* **Action**:アプリケーションでなにが起きたのかを示すオブジェクトデータ
* **Reducer**:Actionの種類に応じて状態を変化させるメソッド
* **Store**:アプリケーションの全ての状態を保持している場所
* **connect()**:React ComponentとReduxを接続するためのメソッド
* **Provider**:connect()でComponentをStoreに接続するために必要なもの

各要素の役割とデータフローをしっかり抑えておくことが重要です。
Reduxについては個人的に理解するのがなかなか大変で、まだ完全には理解できていない面もあるので今後も勉強していきたいと思います。


#参考資料

* [Redux公式サイト](https://redux.js.org/)
* [React Redux公式サイト](https://react-redux.js.org/)
* [アプリの状態管理を安全に行うためのFluxとRedux](https://codezine.jp/article/detail/10751)
* [Reduxを分かりやすく解説してみた](https://future-architect.github.io/articles/20200429/)
* [Redux入門【ダイジェスト版】10分で理解するReduxの基礎](https://qiita.com/kitagawamac/items/49a1f03445b19cf407b7)
* [たぶんこれが一番分かりやすいと思います React + Redux のフロー図解](https://qiita.com/mpyw/items/a816c6380219b1d5a3bf)