Node.js初心者がRedux勉強中です。よく分からなかったのでソースを読んでます。
前の記事では、"Middlewareを使わない場合"を書きました。この記事では"Middlewareを使う場合"を書きます。
この記事で解説する流れは、呼び出し元がこんな風に呼び出している場合を想定しています。
// stateの初期状態無し
let store = createStore(reducerA, applyMiddleware(
middlewareA,
middlewareB
));
// stateの初期状態あり
let store = createStore(reducerA, preloadedState, applyMiddleware(
middlewareA,
middlewareB
));
applyMiddlewareの第1段階
まず最初にapplyMiddleware.jsのapplyMiddleware()を呼び出します。この関数は「関数を返す関数」なので、関数を返します。
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を呼び出し、結果を返しています。
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()の戻り値である関数です。
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関数を上書きしたものを返します。
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にたどり着きます。