用法
為了對(duì)中間件有一個(gè)整體的認(rèn)識(shí)屁置,先從用法開(kāi)始分析扶欣。調(diào)用中間件的代碼如下:
export default function createStore(reducer, preloadedState, enhancer) {
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
}
enhancer
是中間件,且第二個(gè)參數(shù)為 Function
且沒(méi)有第三個(gè)參數(shù)時(shí)蛀骇,可以轉(zhuǎn)移到第二個(gè)參數(shù)评汰,那么就有兩種方式設(shè)置中間件:
const store = createStore(reducer, null, applyMiddleware(...))
const store = createStore(reducer, applyMiddleware(...))
再看 源碼 中間件的傳參:
export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer, preloadedState, enhancer) => {
var store = createStore(reducer, preloadedState, enhancer)
...
}
就是為了得到 store
,并通過(guò) createStore
創(chuàng)建祠肥,上述兩種方法因?yàn)樵?createStore
函數(shù)內(nèi)部傳入了自身函數(shù)才得以實(shí)現(xiàn) :
export default function createStore(reducer, preloadedState, enhancer) {
...
if (typeof enhancer !== 'undefined') {
return enhancer(createStore)(reducer, preloadedState)
}
...
}
上述代碼可以看出武氓,創(chuàng)建 store 的過(guò)程完全交給中間件了,因此開(kāi)啟了中間件第三種使用方式:
const store = applyMiddleware(...)(createStore)
applyMiddleware 源碼解析
大家對(duì)剖析 applyMiddleware
源碼都非常感興趣,因?yàn)樗鼘?shí)現(xiàn)精簡(jiǎn)县恕,但含義甚廣东羹,再重溫其源碼:
export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer, preloadedState, enhancer) => {
var store = createStore(reducer, preloadedState, enhancer)
var dispatch = store.dispatch
var chain = []
var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
假設(shè)大家都已了解 ES6 7 語(yǔ)法,懂得 compose
函數(shù)的含義弱睦,并且看過(guò)一些源碼剖析了百姓,我們才能把重點(diǎn)放在核心原理上:為什么中間件函數(shù)有三個(gè)傳參 store => next => action
渊额,第二個(gè)參數(shù) next
為什么擁有神奇的作用况木?
store
代碼前幾行創(chuàng)建了 store
(如果第三個(gè)參數(shù)是中間件,就會(huì)出現(xiàn)中間件 store 包中間件 store 的情況旬迹,但效果是完全 打平 的), middlewareAPI
這個(gè)變量火惊,其實(shí)就是精簡(jiǎn)的 store
, 因?yàn)樗峁┝?getState
獲取數(shù)據(jù),dispatch
派發(fā)動(dòng)作奔垦。
下一行屹耐,middlewares.map
將這個(gè) store
作為參數(shù)執(zhí)行了一遍中間件,所以中間件第一級(jí)參數(shù) store
就是這么來(lái)的椿猎。
next
下一步我們得到了 chain
, 倒推來(lái)看惶岭,其中每個(gè)中間件只有 next => action
兩級(jí)參數(shù)了。我們假設(shè)只有一個(gè)中間件 fn
犯眠,因此 compose
的效果是:
dispatch = fn(store.dispatch)
那么 next
參數(shù)也知道了按灶,就是 store.dispatch
這個(gè)原始的 dispatch
.
action
代碼的最后,返回了 dispatch
筐咧,我們一般會(huì)這么用:
store.dispatch(action)
等價(jià)于
fn(store.dispatch)(action)
第三個(gè)參數(shù)也來(lái)了鸯旁,它就是用戶(hù)自己傳的 action
.
單一中間件的場(chǎng)景
我們展開(kāi)代碼來(lái)查看一個(gè)中間件的運(yùn)行情況:
fn(middlewareAPI)(store.dispatch)(action)
對(duì)應(yīng) fn
的代碼可能是:
export default store => next => action => {
console.log('beforeState', store.getState())
next(action)
console.log('nextState', store.getState())
}
當(dāng)我們執(zhí)行了 next(action)
后,相當(dāng)于調(diào)用了原始 store
dispatch
方法量蕊,并將 action
傳入其中铺罢,可想而知,下一行輸出的 state
已經(jīng)是更新后的了残炮。
但是 next
僅僅是 store.dispatch
, 為什么叫做 next
我們現(xiàn)在還看不出來(lái)韭赘。
詳見(jiàn) dispatch 后立刻修改 state:
function dispatch(action) {
...
currentState = currentReducer(currentState, action)
...
}
其中還有一段更新監(jiān)聽(tīng)數(shù)組對(duì)象,以達(dá)到 dispatch
過(guò)程不受干擾(快照效果) 作為課后作業(yè)大家獨(dú)立研究:主要思考這段代碼的意圖:https://github.com/reactjs/redux/blob/master/src/createStore.js#L63
多中間件的場(chǎng)景
我們假設(shè)有三個(gè)中間件 fn1
fn2
fn3
, 從源碼的這兩句入手:
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
第一行代碼势就,我們得到了只剩 next => action
參數(shù)的 chain
, 暫且叫做:
cfn1
cfn2
cfn3
, 并且有如下對(duì)應(yīng)關(guān)系
cfnx = fnx(middlewareAPI)
第二行代碼展開(kāi)后是這樣的:
dispatch = cfn1(cfn2(cfn3(store.dispatch)))
可以看到最后傳入的中間件 fn3
最先執(zhí)行辞居。
為了便于后面理解,我先把上面代碼的含義寫(xiě)出來(lái):通過(guò)傳入原始的 store.dispatch
, 希望通過(guò)層層中間件的調(diào)用蛋勺,最后產(chǎn)生一個(gè)新的 dispatch
. 那么實(shí)際上瓦灶,中間件所組成的 dispatch
, 從函數(shù)角度看,就是被執(zhí)行過(guò)一次的 cfn1
cfn2
cfn3
函數(shù)抱完。
我們就算不理解新 dispatch
的含義贼陶,也可以從代碼角度理解:只要執(zhí)行了新的 dispatch
, 中間件函數(shù) cfnx
系列就要被執(zhí)行一次,所以 cfnx
的函數(shù)本身就是中間件的 dispatch
。
對(duì)應(yīng) cfn3
的代碼可能是:
export default next => action => {
next(action)
}
這就是這個(gè)中間件的 dispatch
.
那么執(zhí)行了 cfn3
后碉怔,也就是 dispatch
了之后烘贴,其內(nèi)部可能沒(méi)有返回值,我們叫做 ncfn3
撮胧,大概如下:
export default action => {}
但其函數(shù)自身就是返回值 返回給了 cfn2
作為第一個(gè)參數(shù)桨踪,替代了 cnf3
參數(shù) store.dispatch
的位置。
我們?cè)傧胂耄?code>store.dispatch 的返回值是什么芹啥?不就是 action => {}
這樣的函數(shù)嗎锻离?這樣,一個(gè)中間件的 dispatch
傳遞完成了墓怀。我們理解了多中間件 compose
后可以為什么可以組成一個(gè)新的 dispatch
了(其實(shí)單一中間件也一樣汽纠,但因?yàn)椴襟E只有一步,讓人會(huì)想到直接觸發(fā) store.dispatch
上傀履,多中間件提煉了這個(gè)行為虱朵,上升到組合為新的 dispatch
)。
再解釋 next 的含義
為什么我們?cè)谥虚g件中執(zhí)行 next(action)
钓账,下一步就能拿到修改過(guò)的 store
?
對(duì)于 cfn3
來(lái)說(shuō)碴犬, next
就是 store.dispatch
。我們先不考慮它為什么是 next
, 但執(zhí)行它了就會(huì)直接執(zhí)行 store.dispatch
梆暮,后面立馬拿到修改后的數(shù)據(jù)不奇怪吧服协。
對(duì)于 cfn2
來(lái)說(shuō),next
就是 cfn3
執(zhí)行后的返回值(執(zhí)行后也還是個(gè)函數(shù)惕蹄,內(nèi)層并沒(méi)有執(zhí)行)蚯涮,我們分為兩種情況:
-
cfn3
沒(méi)有執(zhí)行next(action)
,那cfn1
cfn2
都沒(méi)法執(zhí)行store.dispatch
卖陵,因?yàn)樵嫉?dispatch
沒(méi)有傳遞下去遭顶,你會(huì)發(fā)現(xiàn)dispatch
函數(shù)被中間件搞失效了(所以中間件還可以搗亂)。為了防止中間件瞎搗亂泪蔫,在中間件正常的情況請(qǐng)執(zhí)行next(action)
.
這就是
redux-thunk
的核心思想棒旗,如果action
是個(gè)function
,就故意執(zhí)行action
, 而不執(zhí)行next(action)
, 等于讓store.dispatch
失效了撩荣!但其目的是明確的铣揉,因?yàn)闀?huì)把dispatch
返回給用戶(hù),讓用戶(hù)自己調(diào)用餐曹,正常使用是不會(huì)把流程停下來(lái)的逛拱。
-
cfn3
執(zhí)行了next(action)
, 那cfn2
什么時(shí)候執(zhí)行next(action)
台猴,cfn3
就什么時(shí)候執(zhí)行next(action) => store.dispatch(action)
, 所以這一步的next
效果與cfn3
相同朽合,繼續(xù)傳遞下去也同理俱两。我看了下redux-logger
的文檔,果然央求用戶(hù)把自己放在最后一個(gè)曹步,其原因是害怕最右邊的中間件『搗亂』宪彩,不執(zhí)行next(action)
, 那logger
再執(zhí)行next(action)
也無(wú)法真正觸發(fā)dispatch
.
我在考慮這樣會(huì)不會(huì)有很大的局限性,但后來(lái)發(fā)現(xiàn)讲婚,只要中間件常規(guī)情況執(zhí)行了
next(action)
就能保證原始的dispatch
可以被繼續(xù)分發(fā)下去尿孔。只要每個(gè)中間件都按照這個(gè)套路來(lái),next(action)
的效果就與yield
類(lèi)似筹麸。
所以 next
并不是完全意義上的洋蔥模型活合,只能說(shuō)符合規(guī)范(默認(rèn)都執(zhí)行了 next(action)
)的中間件才符合洋蔥模型。
koa 的洋蔥模型可是有技術(shù)保證的竹捉,
generator
可不會(huì)受到代碼的影響芜辕,而redux
中間件的洋蔥模型尚骄,會(huì)因?yàn)槟骋粚硬粓?zhí)行next(action)
而中斷块差,而且從右開(kāi)始直接切斷。
為什么在中間件直接 store.dispatch(action)
倔丈,傳遞就會(huì)中斷憨闰?
理解了上面說(shuō)的話(huà),就很簡(jiǎn)單了需五,并不是 store.dispatch(action)
中斷了原始 dispatch
的傳遞鹉动,而是你執(zhí)行完以后不調(diào)用 next
函數(shù)中斷了傳遞。
總結(jié)
還是要畫(huà)個(gè)圖總結(jié)一下宏邮,在不想看文字的時(shí)候: