ReduxのcreateStore()の処理を追う(Middleware有りの場合)

Node.js初心者がRedux勉強中です。よく分からなかったのでソースを読んでます。

[前の記事](https://qiita.com/paming/items/a333eb9019c50e4c7e6b)では、"Middlewareを使わない場合"を書きました。この記事では"Middlewareを使う場合"を書きます。

この記事で解説する流れは、呼び出し元がこんな風に呼び出している場合を想定しています。

```javascript:index.js
 // stateの初期状態無し
 let store = createStore(reducerA, applyMiddleware(
 	middlewareA,
 	middlewareB
 ));
 // stateの初期状態あり
 let store = createStore(reducerA, preloadedState, applyMiddleware(
 	middlewareA,
 	middlewareB
 ));
```

# applyMiddlewareの第１段階
まず最初に[applyMiddleware.js](https://github.com/reduxjs/redux/blob/v4.0.1/src/applyMiddleware.js)のapplyMiddleware()を呼び出します。この関数は「関数を返す関数」なので、関数を返します。

```javascript:applyMiddleware.js
export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
  }
}
```
applyMiddleware()の返す関数は、「createStoreを引数に取り、「...argsを引数に取って何かしらを返す関数」を返す関数」です。日本語にするとややこしいです。このへんを上手に表現する良い方法は無いのでしょうか？

# createStore()の処理
reducerとapplyMiddleware()で作成した関数(=enhancer)がcreateStore()に入ってきます。
## 引数の整理と型チェックとenhancerの実行
ここではすこし「泥臭く」、引数のチェックと入れ替えを行っています。
createStore()の引数のうち、preloadedStateとenhancerは省略されてる場合があります。preloadedStateが省略されてenhancerが有る場合、preloadedStateの位置にenhancerが格納されているので、これを入れ替えています。
最後に、enhancerを呼び出し、結果を返しています。

```javascript:createStore.js
export default function createStore(reducer, preloadedState, enhancer) {
  if (
    (typeof preloadedState === 'function' && typeof enhancer === 'function') ||
    (typeof enhancer === 'function' && typeof arguments[3] === 'function')
  ) {
    throw new Error(
      'It looks like you are passing several store enhancers to ' +
        'createStore(). This is not supported. Instead, compose them ' +
        'together to a single function'
    )
  }

  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }

    return enhancer(createStore)(reducer, preloadedState)
  }
```

### enhancer(createState)の処理内容
enhancerはapplyMiddleware()の戻り値である関数です。

```javascript:applyMiddleware.js
export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
```
↑の中で、enhancerは"createStore => (...args) => {}"部分、createStoreはcreateStore部分、reducerとpreloadedStateは...args部分に対応しています。
createStateは、呼び出し元から渡ってきた変数ですが、大本ではcreateState.jsのcreateState()関数を指定しています。
```enhancer(createState)```は結果として「...argsを引数として取り何かを返す関数」を返しています。

###  enhancer(createStore)(reducer, preloadedState)の処理内容
ここではStoreオブジェクトの生成と、Middlewareの初期化(?)とチェインの作成し、Storeオブジェクトのdispatch関数を上書きしたものを返します。

```javascript:applyMiddleware.js
    const store = createStore(...args)
    let dispatch = () => {
      throw new Error(
        `Dispatching while constructing your middleware is not allowed. ` +
          `Other middleware would not be applied to this dispatch.`
      )
    }

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
```
まず```const store = createStore(...args)```にてstoreオブジェクトが生成されます。ここの..argsは呼び出し元のreducer, preloadedStateなので、[ReduxのcreateStore()の処理を追う(Middlewareを使わない場合)](https://qiita.com/paming/items/a333eb9019c50e4c7e6b)の処理に入っていきます。

面白い事に、最初dispatchには「エラーを飛ばす関数」が割り当てられています。チェインを作成した後、チェインを作成した後に、その先頭をdispatchに上書きしています。
この辺不要なんじゃないか？とは思うのですが、何か理由がありそうです。また別の機会に調べようと思います。



また、```dispatch = compose(...chain)(store.dispatch)``` ここの理解も大変でした。結果としては「actionを受け取りnext(action)で次に渡っていく関数のチェイン」が出来上がるのですが、composeとMiddlewareの書き方両方でどう動くのかを理解するまで時間がかかりました。このへんもそのうち別記事で書きたいと思います。

最後のreturnですが、storeオブジェクトの各要素を展開した上で、dispatchは別のものに書き換えています。これによって、store.dispatch()の再に、まずMiddleware層が動き、その後にcreateStore()で生成されたdispatch関数にたどり着くようになります。

このretuenで戻されるstoreオブジェクトが、呼び出し元まで戻っていきます。

# 結果どのような形のオブジェクトが生成されるのか
createStore()で生成されるstoreオブジェクトのうち、dispatch関数が、applyMiddlewareで組み立てられたチェインの先頭に置き換わります。
applyMiddlewareは各Middlewareの中でnext(action)を呼び出して次に行きますが、最後にはcreateStore()で生成されるdispatchにたどり着きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7946/cd0dc95b-6e5b-246c-5e8a-9f10e717ee7a.png)