5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-04-30

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

前の記事では、"Middlewareを使わない場合"を書きました。この記事では"Middlewareを使う場合"を書きます。

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

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

applyMiddlewareの第1段階

まず最初にapplyMiddleware.jsのapplyMiddleware()を呼び出します。この関数は「関数を返す関数」なので、関数を返します。

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を呼び出し、結果を返しています。

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()の戻り値である関数です。

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関数を上書きしたものを返します。

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を使わない場合)の処理に入っていきます。

面白い事に、最初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

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?