Django+Reactで学ぶプログラミング基礎(35): Reduxチュートリアル(TodoアプリのStoreをカスタマイズ)

##### [前回] [Django+Reactで学ぶプログラミング基礎(34): Reduxチュートリアル(TodoアプリのStore)](https://qiita.com/mingchun_zhao/items/a4b5142ac45ce9fd5b81)

## はじめに

前回は、TodoアプリのStoreを実装しました。
今回は、Storeのカスタマイズ方法を勉強します。

## 今回の内容

[Todoアプリの構築](https://redux.js.org/tutorials/fundamentals/part-4-store#configuring-the-store)

- Redux Store
    - Store Enhancer
    - Redux Middleware

## Store Enhancer

### Store Enhancerとは
- Storeをカスタマイズし、機能拡張するためのもの
    - Store作成時に、`createStore`の第3引数として渡される
    - 元の`dispatch/getState/subscribe`を書き換えることで、Storeを拡張

- Store Enhancerサンプル
    - `src/exampleAddons/enhancers.js`
        - `sayHiOnDispatch`
            - actionがdispatchされる度に`'Hi'!`をコンソールにログ出力
        - `includeMeaningOfLife`
            - `getState()`の返却値にフィールド`meaningOfLife: 42`を追加

### `sayHiOnDispatch`Enhancerを使用しStoreを作成

- まず、`sayHiOnDispatch`をインポートし、`createStore`の第3引数に渡す
    - `preloadedState`値がないため、`undefined`を第2引数として渡す
```src/store.js
import { createStore } from 'redux'
import rootReducer from './reducer'
import { sayHiOnDispatch } from './exampleAddons/enhancers'

const store = createStore(rootReducer, undefined, sayHiOnDispatch)

export default store
```

- 次に、actionをdispatch
    - `sayHiOnDispatch`Enhancerは、元の`store.dispatch`関数を独自のdispatchでラップ
        - `store.dispatch()`呼び出しは、実際`sayHiOnDispatch`からラッパー関数を呼び出す
            - 元の関数を呼び出してから`Hi!`を出力
```src/index.js
import store from './store'

console.log('Dispatching action')
store.dispatch({ type: 'todos/todoAdded', payload: 'Learn about actions' })
console.log('Dispatch complete')
```

- VS Codeのターミナルで、アプリを起動すると、ブラウザが開かれる
```
C:\kanban\todo>cd redux-fundamentals-example-app
C:\kanban\todo\redux-fundamentals-example-app>npm start
```

- ブラウザで、デベロッパーツールを開き(F12)、コンソールの出力を確認
    - `Hi!`と表示される
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/c38fb04d-383e-a58d-7b14-bddce29525a8.png)

### `includeMeaningOfLife`Enhancerを使用し、storeを作成

- `createStore`の第3引数は、1つのEnhancerのみ受け入れられる
    - 2つのEnhancerを同時に渡すには1つにマージする必要あり

- Reduxコアの`compose`関数を使用し、複数Enhancerをマージ
    - `sayHiOnDispatch`と`includeMeaningOfLife`
```src/store.js
import { createStore, compose } from 'redux'
import rootReducer from './reducer'
import {
  sayHiOnDispatch,
  includeMeaningOfLife
} from './exampleAddons/enhancers'

const composedEnhancer = compose(sayHiOnDispatch, includeMeaningOfLife)

const store = createStore(rootReducer, undefined, composedEnhancer)

export default store
```

- 作成したstoreを使ってみる
    - 二つのEnhancerが同時にstoreの動作を変更している
        - `sayHiOnDispatch`は`dispatch`の動作を変更
            - `'Hi'!`をログ出力
        - `includeMeaningOfLife`は`getState`の動作を変更
            - フィールド`meaningOfLife: 42`を追加

```src/index.js
import store from './store'

store.dispatch({ type: 'todos/todoAdded', payload: 'Learn about actions' })
// log: 'Hi!'

console.log('State after dispatch: ', store.getState())
// log: {todos: [...], filters: {status, colors}, meaningOfLife: 42}
```

- ブラウザで、デベロッパーツールのコンソールを確認
    - `'Hi'!`がログ出力される
    - フィールド`meaningOfLife: 42`が追加される
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/e945356a-e8af-bbce-1343-61d6f5066c08.png)

- Store Enhancerのまとめ
    - Storeを変更する強力な手段
        - `dispatch、getState、subscribe`のいずれかをオーバーライドまたは置換できる
    - Reduxアプリのstoreセットアップ時、少なくとも1つのEnhancerが含まれる

## ミドルウェア(Middleware)

### Middlewareとは
- リクエストを受信するフレームワークとレスポンスを生成するフレームワークの間に配置できるコード
    - Middlewareの特徴は、チェーン構造(パイプライン)
    - 1つのプロジェクトで複数の独立したサードパーティMiddlewareを使用できる

### Redux Middlewareとは

- Reduxの拡張機能(Addon)で、actionのdispatchをカスタマイズする
- actionをdispatchしてからreducerに到達するまで、以下拡張を実施可能
    - ロギング
    - クラッシュレポート
    - 非同期APIとの通信
    - ルーティング
- ※ Reducerと異なり、Middlewareでは、タイムアウトやその他非同期ロジックによる副作用が発生する可能性あり

### Redux Middlewareをstoreに追加

- Redux Middlewareは、Redux組み込みの`applyMiddleware`Store Enhancer上に実装されている
- まず、`applyMiddleware`を使用し、3つのMiddlewareを追加
    - これらのMiddlewareは、actionがdispatchされると番号を出力する
```src/store.js
import { createStore, applyMiddleware } from 'redux'
import rootReducer from './reducer'
import { print1, print2, print3 } from './exampleAddons/middleware'

const middlewareEnhancer = applyMiddleware(print1, print2, print3)

// Pass enhancer as the second arg, since there's no preloadedState
const store = createStore(rootReducer, middlewareEnhancer)

export default store
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/c2590540-a521-9d03-0219-dcbff646b3ca.png)

- 次に、actionをdispatch
```src/index.js
import store from './store'

store.dispatch({ type: 'todos/todoAdded', payload: 'Learn about actions' })
// log: '1'
// log: '2'
// log: '3'
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/e59dc7d3-2473-d3e3-fcbd-939928961d29.png)

- ブラウザで、デベロッパーツールを開き(F12)、コンソールの出力を確認
    - 三つのMiddlewareにより、`1`、`2`、`3`が出力される
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/cfcb3700-6a6e-b31f-76f3-0969e2a88710.png)

### Middlewareのパイプライン

- storeのdispatchに、下記Middlewareのパイプラインが形成される
    - `store.dispatch(action)`を呼び出すと
        - パイプラインの最初のMiddlewareが呼び出される
            - Middlewareは、actionのtypeが処理対象か確認
                - 処理対象の場合、Middlewareはカスタムロジックを実行
                - 処理対象でない場合、actionをパイプライン内の次のMiddlewareに渡す

- Middlewareパイプラインで、actionが渡される順番
    - 1\. `print1`Middleware(store.dispatchと見なされる)
    - 2\. `print2`Middleware
    - 3\. `print3`Middleware
    - 4\. 元のstore.dispatch
    - 5\. store内のroot reducer
- ※ 上記はすべて関数呼び出しで、コールスタックからreturnされる
    - よって、`print1`Middlewareが最初に実行され、最後に終了

## おわりに

ReduxのEnhancerとMiddlewareを使用し、Storeをカスタマイズしました。
次回も続きます。お楽しみに。

##### [次回] [Django+Reactで学ぶプログラミング基礎(36): Reduxチュートリアル(MiddlewareでStoreをカスタマイズ)](https://qiita.com/mingchun_zhao/items/98c027531849c1114411)