redux的中介軟體applyMiddleware原始碼詳解
原文: redux的applyMiddleware原始碼 – 歡迎 watch || star
記得之前第一次看redux
原始碼的時候是很懵逼的,尤其是看到applyMiddleware
函式的時候,更是懵逼。當然那也是半年前的事情了,前幾天把redux
原始碼看了下,並且實現了個簡單的redux
功能。但是沒有實現中介軟體。今天突然又想看看redux
的中介軟體,就看了起來。
記得半年之前是從函式宣告的下一行就開始看不懂了。。。然後前段時間,看了下柯里化函式,加深了高階函式的印象,所以今天打算把中介軟體的原始碼給擼一下。
我們來看看函式宣告的下一行,也就是原始碼第二行開始看:
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
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
}
}
}
從上面我們可以看到中介軟體返回了函式,返回第一個函式是攜帶createStore
引數的,這個是啥?從名字上我們就可以知道,就是createStore
。不過為了證明,我們還是得從原始碼上來看。
還記得是怎麼呼叫的中介軟體的吧,大致如下:
const store = createStore(
reducer,
applyMiddleware(...middlewares)
);
可以看到中介軟體是在createStore
引數裡呼叫的(在引數裡執行函式,導致傳遞給createStore
的是中介軟體執行後返回的結果,從上面的中介軟體原始碼可以知道,返回的就是攜帶createStore
引數的函式),現在我們可以進createStore
函式裡看看他是怎麼處理中介軟體返回的函式的。
redux
的主要實現都是在createStore
裡實現的,所以我們主要看createStore
裡處理引數的部分:
export default function createStore(reducer, preloadedState, enhancer) {
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)
}
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
//other code
}
從我們呼叫createStore
可以知道第一個引數是reducer
,第二個引數就是中介軟體執行之後返回的攜帶createStore
引數的函式。但是在上面的這段原始碼裡,我們發現是preloadedState
來接收這個攜帶createStore
引數的函式,感覺不是很多,命名的’不好’。先繼續往下看,wow, 是一個判斷,他會判斷preloadedState
是不是一個函式,第三個引數enhancer
是不是未定義;如果preloadedState
是函式,enhancer
是未定義,那麼就會把preloadedState
賦值給enhancer
,並且設定preloadedState
是未定義。 這樣就沒有問題了,在這裡,相當於第三個引數enhancer
接收了攜帶createStore
引數的函式。
然後第二個判斷:
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
他會去執行這個enhancer
。這個enhancer
是什麼?就是我們說的攜帶createStore
的函式。
有意思的是,這個enhancer
直接在這裡運行了,並且採用了createStore
作為引數(這個createStore
就是函式呀)。 我們再來看看enhancer(createStore)
返回的是啥:
return function (...args){
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
}
}
有意思,返回的是帶有多個引數的函式。
上面的程式碼相當於:
enhancer(createStore) ~= function(...args) => function(reducer, preloadedState)
可以看到,上面的(...args)
就是相當於(reducer, preloadedState)
。
那麼我們再來看看上面的function(...args)
, 額, 直接在第一行就再次呼叫建立store
,這樣不會陷入無限迴圈嗎?不會,因為有引數判斷,在createStore
的原方法裡不會再執行enhancer
; 所以我們可以發現,在有中介軟體的時候,真正的執行createStore
是在中介軟體裡去執行的,並且攜帶的引數是reducer, preloadedState
。
所以上面第一行建立了個store
物件,他返回的屬性有:
{
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
然後新建了個指向dispatch
變數的匿名函式,這個函式在呼叫的時候丟擲異常告訴你不可以在構造中介軟體時呼叫dispatch
。
Dispatching while constructing your middleware is not allowed. Other middleware would not be applied to this dispatch.
接下來會建立一個middlewareAPI
物件:
const middlewareAPI = {
getState: store.getState, //獲取store裡的state
dispatch: (...args) => dispatch(...args) // 呼叫`dispatch`的時候會拋錯,如果在組合中介軟體之前呼叫,下面會說
}
一開始我以為是在呼叫的時候就會報錯,可是發現這個物件裡的dispatch
攜帶引數,如果只是單純拋錯,完全可以不需要傳遞引數,然後向下看下去才看到其中的奧妙。
然後就是對中介軟體集合middlewares
(陣列)進行操作:
const chain = middlewares.map(middleware => middleware(middlewareAPI)) //返回了新的集合,對應的每個中介軟體呼叫的結果
然後就是組合這些中介軟體了, 這裡對高階函式不熟的,可以看下柯里化函式和函式組合::
// china是上面返回的中介軟體的結果
dispatch = compose(...chain)(store.dispatch)
可以看到這個程式碼,組合了中介軟體, 使用compose
這個高階函式來處理的。我們看下這個高階函式:
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
上面的程式碼比較有意思:
- 第一個判斷
如果沒有中介軟體作為引數傳遞,那麼直接返回一個函式,接收對應的引數並且返回這個引數。 - 第二個判斷
如果如果這個中介軟體引數只有一個,那麼直接返回這個中介軟體函式 - 最後一步
那就是多箇中間件傳遞進來的時候,他會借用reduce
方法組合(這個放在後面), 會有個...args
引數,就是(store.dispatch
),等下回說到。
可能你對reduce
不是很熟,可以簡單的看下他幹了什麼事:
['1', 2, 3, 'n'].reduce((a, b) => console.log('a is',a , 'b is', b)) // 這樣你就會發現這個方法在這裡的作用
其實從註釋裡也可以知道:
* @param {...Function} funcs The functions to compose. * @returns {Function} A function obtained by composing the argument functions * from right to left. For example, compose(f, g, h) is identical to doing * (...args) => f(g(h(...args))).
把這些中介軟體都執行到dispatch
.
再回到上面看compose
的返回:
return funcs.reduce((a, b) => (...args) => a(b(...args)))
我們再看看中介軟體呼叫compose
的地方:
dispatch = compose(...chain)(store.dispatch)
從這個地方再配合看compose(...chain) => result
的這個result
.
- 第一個判斷
返回的是(arg) => arg
就是相當於result(arg) => arg
, 果然,直接返回這個store.dispatch
- 第二個判斷
返回的是唯一的一箇中間件result
. 然後中介軟體直接呼叫store.dispatch
作為引數。 - 最後一個
這個返回的是一個函式,看起來像這樣:
(...args) => a(b(...args))
這樣就相當於result(args) => a(b(...args))
,這樣就保證每個中介軟體都會用到dispatch
,並且最終返回這個被擴充套件過的dispatch
.
然後可以看到中介軟體函式返回了物件:
{
...store,
dispatch
}
這個dispatch
就是被處理過的dispatch
。