爲了對中間件有一個總體的認識,先從用法開始分析。調用中間件的代碼以下:前端
源碼 createStore.js#39react
export default function createStore(reducer, preloadedState, enhancer) {
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
}複製代碼
enhancer
是中間件,且第二個參數爲 Function
且沒有第三個參數時,能夠轉移到第二個參數,那麼就有兩種方式設置中間件:git
const store = createStore(reducer, null, applyMiddleware(...))複製代碼
const store = createStore(reducer, applyMiddleware(...))複製代碼
再看 源碼 中間件的傳參:github
export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer, preloadedState, enhancer) => {
var store = createStore(reducer, preloadedState, enhancer)
...
}複製代碼
就是爲了獲得 store
,並經過 createStore
建立,上述兩種方法由於在 createStore
函數內部傳入了自身函數才得以實現 :redux
export default function createStore(reducer, preloadedState, enhancer) {
...
if (typeof enhancer !== 'undefined') {
return enhancer(createStore)(reducer, preloadedState)
}
...
}複製代碼
上述代碼能夠看出,建立 store 的過程徹底交給中間件了,所以開啓了中間件第三種使用方式:數組
const store = applyMiddleware(...)(createStore)複製代碼
你們對剖析 applyMiddleware
源碼都很是感興趣,由於它實現精簡,但含義甚廣,再重溫其源碼:微信
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
}
}
}複製代碼
假設你們都已瞭解 ES6 7 語法,懂得 compose
函數的含義,而且看過一些源碼剖析了,咱們才能把重點放在覈心原理上:爲何中間件函數有三個傳參 store => next => action
,第二個參數 next
爲何擁有神奇的做用?app
代碼前幾行建立了 store
(若是第三個參數是中間件,就會出現中間件 store 包中間件 store 的狀況,但效果是徹底 打平 的), middlewareAPI
這個變量,其實就是精簡的 store
, 由於它提供了 getState
獲取數據,dispatch
派發動做。frontend
下一行,middlewares.map
將這個 store
做爲參數執行了一遍中間件,因此中間件第一級參數 store
就是這麼來的。koa
下一步咱們獲得了 chain
, 倒推來看,其中每一箇中間件只有 next => action
兩級參數了。咱們假設只有一箇中間件 fn
,所以 compose
的效果是:
dispatch = fn(store.dispatch)複製代碼
那麼 next
參數也知道了,就是 store.dispatch
這個原始的 dispatch
.
代碼的最後,返回了 dispatch
,咱們通常會這麼用:
store.dispatch(action)複製代碼
等價於
fn(store.dispatch)(action)複製代碼
第三個參數也來了,它就是用戶本身傳的 action
.
咱們展開代碼來查看一箇中間件的運行狀況:
fn(middlewareAPI)(store.dispatch)(action)複製代碼
對應 fn
的代碼多是:
export default store => next => action => {
console.log('beforeState', store.getState())
next(action)
console.log('nextState', store.getState())
}複製代碼
當咱們執行了 next(action)
後,至關於調用了原始 store
dispatch
方法,並將 action
傳入其中,可想而知,下一行輸出的 state
已是更新後的了。
可是 next
僅僅是 store.dispatch
, 爲何叫作 next
咱們如今還看不出來。
function dispatch(action) {
...
currentState = currentReducer(currentState, action)
...
}複製代碼
其中還有一段更新監聽數組對象,以達到 dispatch
過程不受干擾(快照效果) 做爲課後做業你們獨立研究:主要思考這段代碼的意圖:github.com/reactjs/red…
咱們假設有三個中間件 fn1
fn2
fn3
, 從源碼的這兩句入手:
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)複製代碼
第一行代碼,咱們獲得了只剩 next => action
參數的 chain
, 暫且叫作:
cfn1
cfn2
cfn3
, 而且有以下對應關係
cfnx = fnx(middlewareAPI)複製代碼
第二行代碼展開後是這樣的:
dispatch = cfn1(cfn2(cfn3(store.dispatch)))複製代碼
能夠看到最後傳入的中間件 fn3
最早執行。
爲了便於後面理解,我先把上面代碼的含義寫出來:經過傳入原始的 store.dispatch
, 但願經過層層中間件的調用,最後產生一個新的 dispatch
. 那麼實際上,中間件所組成的 dispatch
, 從函數角度看,就是被執行過一次的 cfn1
cfn2
cfn3
函數。
咱們就算不理解新 dispatch
的含義,也能夠從代碼角度理解:只要執行了新的 dispatch
, 中間件函數 cfnx
系列就要被執行一次,因此 cfnx
的函數自己就是中間件的 dispatch
。
對應 cfn3
的代碼多是:
export default next => action => {
next(action)
}複製代碼
這就是這個中間件的 dispatch
.
那麼執行了 cfn3
後,也就是 dispatch
了以後,其內部可能沒有返回值,咱們叫作 ncfn3
,大概以下:
export default action => {}複製代碼
但其函數自身就是返回值 返回給了 cfn2
做爲第一個參數,替代了 cnf3
參數 store.dispatch
的位置。
咱們再想一想,store.dispatch
的返回值是什麼?不就是 action => {}
這樣的函數嗎?這樣,一箇中間件的 dispatch
傳遞完成了。咱們理解了多中間件 compose
後能夠爲何能夠組成一個新的 dispatch
了(其實單一中間件也同樣,但由於步驟只有一步,讓人會想到直接觸發 store.dispatch
上,多中間件提煉了這個行爲,上升到組合爲新的 dispatch
)。
next(action)
,下一步就能拿到修改過的 store
?對於 cfn3
來講, next
就是 store.dispatch
。咱們先不考慮它爲何是 next
, 但執行它了就會直接執行 store.dispatch
,後面立馬拿到修改後的數據不奇怪吧。
對於 cfn2
來講,next
就是 cfn3
執行後的返回值(執行後也仍是個函數,內層並無執行),咱們分爲兩種狀況:
cfn3
沒有執行 next(action)
,那 cfn1
cfn2
都無法執行 store.dispatch
,由於原始的 dispatch
沒有傳遞下去,你會發現 dispatch
函數被中間件搞失效了(因此中間件還能夠搗亂)。爲了防止中間件瞎搗亂,在中間件正常的狀況請執行 next(action)
.這就是
redux-thunk
的核心思想,若是action
是個function
,就故意執行action
, 而不執行next(action)
, 等於讓store.dispatch
失效了!但其目的是明確的,由於會把dispatch
返回給用戶,讓用戶本身調用,正常使用是不會把流程停下來的。
cfn3
執行了 next(action)
, 那 cfn2
何時執行 next(action)
,cfn3
就何時執行 next(action) => store.dispatch(action)
, 因此這一步的 next
效果與 cfn3
相同,繼續傳遞下去也同理。我看了下 redux-logger
的文檔,果真央求用戶把本身放在最後一個,其緣由是懼怕最右邊的中間件『搗亂』,不執行 next(action)
, 那 logger
再執行 next(action)
也沒法真正觸發 dispatch
.我在考慮這樣會不會有很大的侷限性,但後來發現,只要中間件常規狀況執行了
next(action)
就能保證原始的dispatch
能夠被繼續分發下去。只要每一箇中間件都按照這個套路來,next(action)
的效果就與yield
相似。
因此 next
並非徹底意義上的洋蔥模型,只能說符合規範(默認都執行了 next(action)
)的中間件才符合洋蔥模型。
koa 的洋蔥模型但是有技術保證的,
generator
可不會受到代碼的影響,而redux
中間件的洋蔥模型,會由於某一層不執行next(action)
而中斷,並且從右開始直接切斷。
store.dispatch(action)
,傳遞就會中斷?理解了上面說的話,就很簡單了,並非 store.dispatch(action)
中斷了原始 dispatch
的傳遞,而是你執行完之後不調用 next
函數中斷了傳遞。
仍是要畫個圖總結一下,在不想看文字的時候:
本文對你有幫助?歡迎掃碼加入前端學習小組微信羣: