React-Redux超入門(3)

### 概要
[前回](https://qiita.com/hiro4669/items/c9fde1076a3e688d25ec)までのアプリケーションに，Reduxを導入します．ReduxはFluxという概念を導入したフレームワークです．Fluxに関しては，様々な記事があるので，[ググって](https://www.google.co.jp/search?q=flux+javascript&oq=flux+javascript&aqs=chrome..69i57j0l5.4223j1j9&sourceid=chrome&ie=UTF-8)みて下さい．Reduxはフレームワークなので，アプリケーションを実装するときに決まった手順で実装していくことになります．具体的には，Reduxでは，以下のコンポーネントが登場します．

- Store : ステートを管理するコンポーネント
- Reducer : アクションとステートを受け取り，**新しいステート**を作って返すコンポーネント
- Action : 任意のオブジェクト．ただし，Flux Standard Action形式に従うのが普通
- ActionCreator : アクションを生成するコンポーネント

とりあえず，この4つとそれぞれの関係を理解しておけば十分だと思う．

### Redux概要(個人的な理解)

#### Fluxとの関係
Reduxは，Fluxを実現するフレームワークの1つである．Fluxでは，
Action->dispatcher->Store->View->Action...のように，Actionがディスパッチされれ，その結果ステートが変化し，ステートの状態を監視しているViewに変更が加えられる，という風にアプリケーションを開発・実行する．

FluxとReduxの関係を表にまとめてみた．

|Flux  |Redux  |
|---|---|
|Action  |Action(ActionCreator)  |
|dispatcher  |Store.dispatch  |
|store  |Store(Reducer)  |

このように開発スタイルを決めることによって，ある現象が起きたときにどこを見ればよいかはっきりするのでバグの混入やミスを少なくできる．また，デバッグするときにも効率よく実施できる．

#### Reactとの関係
基本的にReduxはReactとは無関係である．Reduxは開発スタイルを統一するフレームワークで，ReactはViewのフレームワークであり，お互い基本的には関係ない．しかし，Reactだけで開発を進めると，開発規模が大きくなったときに破綻してくる．そのため，開発スタイルを統一するReduxを導入したほうが良い，ということになる．これは，GUIアプリケーション開発でMVCアーキテクチャを導入したほうが良い，という関係と同じである．

そして，ReactはFluxのViewの役割を果たすフレームワークで，ステートの変更によって必要なコンポーネントが再描画されるが，ステートはComponentを継承するクラスコンポーネント(関数コンポーネントはステートを持たない)がそれぞれ持っていて，どのコンポーネントが持つステートが変更されると，どのViewコンポーネントが変更されるのか変更されるのか認識していないといけないため，規模が大きくなると処理が煩雑になる．そのため，ステートを一元的に管理し，**どのコンポーネントも1つのステートを監視する**，という方針でアプリケーションを構築するのがReduxである．そして，そのステートを管理し，dispatchメソッドを備えるのがStoreクラス，となっている．
よって，アプリケーション全体に関係するようなステートはすべてStoreで管理すべきである．一方，各コンポーネントのステートはStoreとは独立しているので，各コンポーネントで個々にステートを使うこともできる．この使い分けは，あるViewコンポーネントに限定した処理を行いたい(例えばUIだけの変更)場合は各コンポーネントのステートを用い，アプリケーション全体に関わるものはStoreで管理すればよいのだろう．

### 準備
Reduxをインストールします

```
>npm install --save redux
```
前回作ったアプリケーションで，`Input3`を使ったやり方をベースにReduxを適用していく．
Reduxを利用する場合，上に示したStore, Reducer, Action, ActionCreatorを作る必要があります．加えて前回作ったアプリケーションもいくつかファイルがありますので，ここでディレクトリ構造を整理する．
src/
　├ index.js // アプリケーションのエントリーポイント．今回は少し修正します．
　├ App.js   // メインコンポーネント．ItemListやInput3などのコンポーネントを含む．
　├ Item.js  // 個々のアイテムを表現する．今回は修正なし
　├ ItemList.js  // アイテムのリストを表現する．今回は修正なし
　├ Input3.js    // 入力フォームを生成します．今回は修正します．
　├ actions/
　　　├ item.js   // 新規作成．ActionCreatorを定義します．
　├ reducers/
　　　├ item.js   // 新規作成．Reducerとステートを初期化します．

これらの修正を以下の手順で行う．
1. Reducerの作成
2. ActionCreatorの作成
3. Input3の各コンポーネントのイベントハンドラの修正．
4. Appの修正
5. index.jsの修正

### Reduxの適用

#### 1． Reducerの作成
Reduxでは，Storeがアプリケーション全体のステートを管理します．そして，何らかのイベントが発生してステートを更新する時には，Reducerが新たなステートを生成します．したがって，Storeは利用するReducerを知る必要があるため，Storeの生成にはReducerが必須になります．ReducerはJavaScriptの関数です．引数に`store`と`action`をとります(両者ともJavaScriptの単なるオブジェクトです)．また，これ以外にも引数が取れますが，ここでは割愛します．
まず，`reducers`ディレクトリを生成し，`item.js`を生成して`reducer`を定義します．

```jsx:reducers/item.js
const initialState = {  //　(1)Storeが管理するステートの初期化
    task: '',
    tasks: [],
};

function appReducer(state = initialState, action) { // (2) reducerの定義
    switch(action.type) {
        case 'INPUT_TASK':
            return {
                ...state,
                task: action.payload.task,
            };
        case 'ADD_TASK':
            const nl = state.tasks.concat([action.payload.task]);
            return {
                ...state,
                tasks: nl
            };
        default:
            return state;
    }
}
export default appReducer;
```

(1)
Storeが管理するステートの初期値を定義する．ここでは，入力フォームの文字列の状態を管理する`task`と，追加される`task`のリストを管理する．

(2)
`addReducer`がReducerの関数となる．引数に`state`と`action`をとる．後述するように，`action`はJavaScriptのオブジェクトですが，Flux Standard Action形式のオブジェクトを前提としている．
Reducerの役目は，**新しいステートオブジェクトを生成して返す**ことである．新しいステートオブジェクトを返すために，どんなアクションを受け取ったか，また直前のステートを利用する．したがって，Reducer本体は巨大な`switch-case`文となり，この分岐に`action`の種類を利用する．例えば，`INPUT_TASK`の場合には，現在のステートに加え，受け取った`action`の`task`を使って新たな`task`を上書きし，新しいステートオブジェクトを返している．ここで，`...state`という表現は，ES6から採用されたスプレッドオペレータという新しい演算子で，オブジェクトや配列の中身を展開する．ここでは`state`は直前のステートを表しているので，そのメンバーは`task`と`tasks`であり，これは以下のようなオブジェクトで表現されている．

```javascript:state
{
  task: xxx,
  tasks: [x,x,x]
}
```
これをスプレッド演算子`...state`を使うと

```javascript:...state
task: xxx,
tasks: [x,x,x]
```
と展開され，更に`{}`で新たなオブジェクトを生成するので

```javascript:{...state}
{
  task: xxx,
  tasks: [x,x,x]
}
```
となるが，ここで`task:action.payload.task`を追加するので，

```javascript:
{
  task: xxx,
  tasks: [x,x,x]
  task: action.payload.task
}
```
となり，最後に追加したメンバーが既存のメンバーを上書きするので，最終的に

```javascript:
{
  tasks: [x,x,x]
  task: action.payload.task
}
```
という新しいオブジェクトが生成され返される，という仕組みになっている．

(2)の内容はここではまだ全部説明できないが，とりあえずReducerを作るだけなら，`default: return state`だけの関数を定義しておけばよい．そして，新たに`Action`を定義したときにその`type`に合わせたステートを生成して返すように修正していけば良い．

#### 2. ActionCreator/Actionの生成
`ActionCreator`は，`Reducer`が受け取る`Action`を生成する関数である．`Reducer`や`Action`がReduxの定めたルールに従うために生成する関数やオブジェクトであるのに対し，`ActionCreator`は任意のJavaScript関数である．よって，　`Action`を返す関数であれば，名前や引数の数に制約はない．しかし，規模が大きくなると破綻してくるので，`actions`ディレクトリを生成し，`item.js`を生成してこの中に定義する．Reducerの定義とファイル名を同じくするのは，実は`reducer`は`combineReducers`という仕組みで，複数のReducerを組み合わせることができ，そのときどのActionCreatorがどのReducerと関連があるのか，ということを識別しやすくするためである．
ここでは，文字が入力される時と，追加ボタンが押された時，の２つのイベントを処理するため，`ActionCreator`を２つ生成する．

```javascript:actions/items.js

export const inputTask = (task) => {　// (1) 文字を入力されるイベント
    return ({
        type: 'INPUT_TASK',
        payload: {
            task
        }
        });
}

export const addTask = (task) => {  // (2)アイテムを追加するイベント
    return ({
        type: 'ADD_TASK',
        payload: {
            task
        }
    });
}
```
(1)
これは，`input`の`onChange`イベントで実行されることを想定している．引数はテキストフィールドに入力された文字列全体を受け取る．そして，アクションを生成して返す．ここで定義したアクションの形式が．Flux Standard Actionである．

```javascript:FluxStandardAction
{
  type: some_type,
  payload: {
  }
}
```
これはJavaScriptのオブジェクトで，２つのメンバーから構成されている．'type'でアクションのタイプを表し，その内容は'payload'というメンバーに任意のオブジェクトとして定義できる．ここでは，実は`payload`は全く同じで，単に`type`だけが異なる２つの`ActionCreator`を定義している．このアプリケーションではとても簡単な定義になっているが，実際のアプリケーションではこの`ActionCreator`にビジネスロジックを書くことになる(と思う)．すなわち，何らかのデータをコンポーネントが`Props`経由で受け取り，`ActionCreator`を必要な引数を与えて呼び出す．`ActionCreator`は受け取った引数などから適切なビジネスロジックを実行し，結果を`Action`に格納して`Reducer`に与える，という感じだろうか．

(2)
これは，`button`の`onClick`イベントで実行されることを想定している．あとは(1)と同様なので割愛．

#### 3. Input3.jsの修正
2で定義した`ActionCreator`を実行するように，`Input3`を修正する．具体的には，イベントハンドラやその中で実行する処理を`ActionCreator`を実行するように変更する．

```jsx:Input3.js
import React, {Component} from 'react';
import {inputTask, addTask} from './actions/items';

class Input3 extends Component {
    constructor(props) {
        super(props);                                //(1)ステートは削除
        this.addItem = this.addItem.bind(this);
    }

    addItem() {
        const {task} = this.props.store.getState();
        this.props.store.dispatch(addTask(task));  // (2)addTaskを実行するように変更
    }

    render() {
        return (
            <div>
                <input type="text" onChange={(e) => 
              {this.props.store.dispatch(inputTask(e.target.value))}} />  // (3)inputTaskを実行するように変更
                <button onClick={this.addItem}>Add</button> 
            </div>
        )
    }
}
export default Input3;
```
(1)
まず，前回このコンポーネントで管理していたステートを削除する．今回はすべて`Store`で管理するためである．

(2)
ボタンを押されたときに実行する処理を変更する．まず，これまでは`Input3`内部でステートを管理し，追加する文字列を取得していたが，Reduxの導入でその情報は`Store`で管理することにした．そのため，`this.props.store.getState()`で`Store`からステートを取り出している．次に，追加された文字列(task)を使って`addTask`を実行している．この返り値は`Action`であり，これを使って`this.props.state.dispatch`を実行している．この`dispatch`は，`
Action`をStoreに届ける仕組みであり，`Flux`の`dispatch`と同義である．`dispatch`が実行されると，`Reducer`が実行され，新たなステートを生成する，という流れになる．ここで，`this.props.store`はどこからやってくるのか疑問だと思うが，これは親のコンポーネントから`Props`経由で渡さなければいけない．詳しくは変更後の`App`を参照．

(3)
(2)と同様に`ActionCreator`を実行するように変更する．なお，ここではイベントハンドラを設定するのではなく，クロージャを直接定義して設定している．

#### 4. Appの修正

```jsx:App
import React, { Component } from 'react';
import './App.css';

import Input3 from './Input3';
import ItemList from './ItemList';

class App extends Component {
  constructor(props) {
      super(props);　　　 // (1) ステートの削除
  }

  render() {
    return (
        <div>
          <h1>Todo App</h1>
            <Input3 store={this.props.store}/>   // (2)Input3にstoreを渡す
          <ul>
            <ItemList items={this.props.store.getState().tasks} /> // (3)ItemListにtasksを渡す
          </ul>
        </div>
    );
  }
}
export default App;
```

(1)
まず，ステートの管理は`Store`で行うため，ステートの初期化を削除する．

(2)
`Input3`では`store`を使う必要があるため，`props`経由で渡す．

(3)
これまでは`App`のステートで`items`を管理していたので，それを`Store`のステート経由で渡すように変更する．

このように，Reduxを導入するとApp自身には状態を保持する必要がなくなる．したがって，関数コンポーネントで定義もできる．また，Appでも`this.props.store`と，自身の`Props`から`store`を取り出しているが，これは当然上位のコンポーネント(`index.js`)から`Props`経由で渡されてくる．

#### 5. index.jsの修正
これまで作成したコンポーネントをつないでいくのが`index.js`の役割となる．

```jsx:index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import appReducer from './reducers/items'; // (1)reducerのインポート

import App from './App';

import {createStore} from 'redux';  // (2)reduxからcreateStoreのインポート

const store = createStore(appReducer); // (3)storeの生成

//ReactDOM.render(<App />, document.getElementById('root'));

function renderApp() {
    ReactDOM.render(<App store={store} />, document.getElementById('root')); // (4)Appにstoreを渡す
}

store.subscribe(()=>renderApp());  // (5)再描画の設定
renderApp();                       // (6)描画の実行
```

(1)
Reducerをインポートしている．

(2)
`Store`を生成するための`createStore`関数を`Redux`からインポートする．

(3)
`appReducer`を引数に，Storeを生成する

(4)
`App`に生成した`store`を`Props`経由で渡す．ここから芋づる式にstoreを子，孫コンポーネントに渡していく．

(5)
`store`には`subscribe`というメソッドがあり，これは`store`の管理するStateが変更されたときに実行するコールバック関数を設定する．実は`Reducer`で新しいステートを返した後，`setState`は明示的に呼んでいない．Reactでは`setState`を呼び出して新たなステートを設定しない限り再描画されない．そこで，このコールバック設定することで，`store`のステートが変更された時に呼び出されるようになり，コールバックで`renderApp`を呼び出すことで全てのコンポーネントの`render`が呼び出されるようになる．

以上の修正によって，これまでのアプリケーションにReduxを組み込むことができた．しかし，今回の修正でわかったように，全てのコンポーネントでstoreが必要になるため，`Props`で引き継いていかなければならない．かつ，アプリケーションが本来の関心事とReduxを使うためのコードが混在してしまっている．そこで次に導入するのがReact-Reduxライブラリである．次は，このアプリケーションをさらに改良し，React-Reduxライブラリを適用していく．[次回](https://qiita.com/hiro4669/items/5683c70ec8d5c7f10059)に続く