“中間件”這個(gè)詞聽起來很恐怖,但它實(shí)際一點(diǎn)都不難。想更好的了解中間件的方法就是看一下那些已經(jīng)實(shí)現(xiàn)了的中間件是怎么工作的,然后嘗試自己寫一個(gè)。函數(shù)嵌套寫法看起來很恐怖,但是大多數(shù)你能找到的中間件,代碼都不超過十行,但是它們的強(qiáng)大來自于它們的可嵌套組合性。
這段話我們提取關(guān)鍵詞后得出:我們需要了解什么是函數(shù)嵌套,中間件就是變態(tài)的函數(shù)嵌套罷了,函數(shù)嵌套大約就是函數(shù)式編程的概念,而使用函數(shù)式編程必須知道的就是柯里化。
需要知道的概念
- 函數(shù)式編程
- 柯里化
- middleware
這里從middleware開始說起(函數(shù)式編程,柯里化概念請(qǐng)自行去理解),就拿異步actionredux-thunk( 簡(jiǎn)寫版)來說
export default const thunkMiddleware =
({ dispatch, getState }) =>
next =>
action =>
typeof action === ‘function’ ?
action(dispatch, getState) :
next(action);
Redux中文文檔如是解釋:
...middlewares (arguments): 遵循 Redux middleware API 的函數(shù)。每個(gè) middleware 接受 Store
的 dispatch
和 getState
函數(shù)作為命名參數(shù),并返回一個(gè)函數(shù)。該函數(shù)會(huì)被傳入 被稱為 next的下一個(gè) middleware 的 dispatch 方法,并返回一個(gè)接收 action 的新函數(shù),這個(gè)函數(shù)可以直接調(diào)用 next(action),或者在其他需要的時(shí)刻調(diào)用,甚至根本不去調(diào)用它。調(diào)用鏈中最后一個(gè) middleware 會(huì)接受真實(shí)的 store 的 dispatch 方法作為 next 參數(shù),并借此結(jié)束調(diào)用鏈。所以,middleware 的函數(shù)簽名是 ({ getState,dispatch }) => next => action
其中store的dispatch和getState方法是在createStore時(shí)通過applyMiddleware注入的。
next方法用于維護(hù)中間件調(diào)用鏈和dispatch,它返回一個(gè)接受action對(duì)象的柯里化函數(shù),接受的action對(duì)象可以在中間件中被修改,再傳遞給下一個(gè)被調(diào)用的中間件,最終dispatch會(huì)使用中間件修改后的action來執(zhí)行。
如果得到的
action是個(gè)函數(shù),就用dispatch和getState當(dāng)作參數(shù)來調(diào)用它,否則就直接分派給store。
再來看看我們異步的action寫法:
export const test = (params)=> {
return (dispatch, getState)=> {
// do something
dispatch(otherAction)
}
}
到這一步,我們對(duì)于為什么要使用redux-thunk中間件有了合理的解釋,還是不理解可以參考:redux-tutorial-cn
創(chuàng)建store的過程
理解middleware我們從創(chuàng)建store的源頭開始,其中createStore最常見的寫法:
const enhancer = compose(
// 應(yīng)用中間件到store中
applyMiddleware(reduxPromiseMiddleware(), funActionThunk, thunk)
)
export default const store = createStore(reducer, initialState, enhancer)
從上得知,入口從createStore方法開始,所以來先瞅一眼createStore.js源碼
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.')
}
// 根據(jù)我們的傳參該函數(shù)會(huì)在此處返回,將其本身作為參數(shù)傳遞給enhancer函數(shù),并再次調(diào)用傳遞剩余參數(shù)
return enhancer(createStore)(reducer, preloadedState)
}
...省略
從上面代碼的注釋處可以看出,調(diào)用了enhancer函數(shù),而enhancer函數(shù)由applyMiddleware后得到,所以再來看看applyMiddleware.js的源碼:
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
}
}
}
此處看到它返回一個(gè)函數(shù),而這個(gè)函數(shù)剛好接受enhancer函數(shù)的參數(shù)createStore,而這個(gè)createStore就是redux封裝的方法,接著返回一個(gè)函數(shù),該函數(shù)的三個(gè)參數(shù)和創(chuàng)建store時(shí)的createStore方法傳的參數(shù)一致,只不過在這里enhancer === ‘undefined’,此時(shí)正好返回了初始化后的store:
var store = createStore(reducer, preloadedState, enhancer)
這個(gè)store包含4個(gè)方法,見createStore源碼:
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
將dispatch用另外變量保存的目的就是為了接下來將dispatch getState注入給所有的middleware:
chain = middlewares.map(middleware => middleware(middlewareAPI))
然后將多個(gè)middleware函數(shù)經(jīng)過compose處理后得到一個(gè)函數(shù),這一步形象點(diǎn)說就是將最初原始的dispatch經(jīng)過一層一層middleware中間件“加工后”重新包裝并返回生成終極dispatch和初始化后的store
進(jìn)一步
理解完了源碼的流程我們最后再來回顧中間件redux-thunk源碼,以此來理解,中間件是如何包裝dispatch的,不然我們討論這些有何意義?
export default const thunkMiddleware =
({ dispatch, getState }) =>
next =>
action =>
typeof action === ‘function’ ?
action(dispatch, getState) :
next(action);
現(xiàn)在我們來看源碼是不是很清晰?中間件只能接受這2個(gè)參數(shù){ dispatch, getState },因?yàn)樗褪?code>middlewareAPI。
- next是什么
中間件返回一個(gè)函數(shù),參數(shù)是next,然后再聯(lián)系applyMiddleware源碼來看
dispatch = compose(...chain)(store.dispatch)
到這里我們不得不來看下compose的作用是什么了
compose 做的只是讓你不使用深度右括號(hào)的情況下來寫深度嵌套的函數(shù) (這里不展開,可自行詳細(xì)了解).
我們可以看到最右側(cè)的middleware傳入的第一個(gè)參數(shù)就是store.dispatch,然后以返回的結(jié)果作為參數(shù)繼續(xù)傳遞給下一個(gè)中間件,最終返回dispatch。所以,這里的next就是最原始的dispatch,或者經(jīng)過前面的middleware處理過后的dispatch。
- action是什么?
上面我們可以看到,最終返回的這個(gè)終極dispatch成了我們?cè)?code>action中調(diào)用的dispatch,讓我們回歸到最初的調(diào)用:
export const test = (params)=> {
return (dispatch, getState)=> { // 這個(gè)dispatch是終極dispatch嗎?答案是
// do something
dispatch(otherAction). // 這個(gè)dispatch是終極版dispatch嗎?答案不是,有可能是最原始的,也有可能是前一個(gè)middleware處理過后的
}
}
這里可以看到,這個(gè)action返回一個(gè)函數(shù),這個(gè)函數(shù)接受的參數(shù)就是我們創(chuàng)建store時(shí)調(diào)用createStore方法返回的參數(shù)(這里只是選取了2個(gè))。要說明的是,這里的dispatch就是終極版的dispatch,中間件里的action是我們?cè)诙xAction時(shí)return的方法或者dispatch的otherAction,所以我們看到例子中的test方法(異步action)返回的是一個(gè)函數(shù),redux-thunk源碼中判斷action是一個(gè)函數(shù)后就返回了一個(gè)函數(shù),這個(gè)函數(shù)的參數(shù)就是return (dispatch, getState)=> {}里的參數(shù),給后面觸發(fā)action用。
- middleware是如何處理“加工”dispatch的?
因?yàn)槲覀兦懊嬲f過:
dispatch = compose(...chain)(store.dispatch)
middleware chain經(jīng)過compose后從右到左執(zhí)行一層一層處理dispatch,其實(shí)這個(gè)表述不準(zhǔn),或者我可以這樣理解:中間件就是一個(gè)閉包?在拿到dispatch后保存在這里,當(dāng)action被觸發(fā)后,經(jīng)過異步的操作后,用這個(gè)dispatch觸發(fā)了需要異步處理結(jié)束后再觸發(fā)的action。(此處理解可能不妥)
這樣一來,也方便我們理解為什么要使用redux-thunk中間件來實(shí)現(xiàn)異步action了,因?yàn)槟憧梢栽赿ispatch之前做其它操作再觸發(fā),感覺就像是閉包的概念,dispatch作為閉包中的變量方法,閉包中含有其它異步操作,直到異步操作完成之后才被觸發(fā)調(diào)用。(這里的閉包概念引用出處:前端基礎(chǔ)進(jìn)階(六):在chrome開發(fā)者工具中觀察函數(shù)調(diào)用棧、作用域鏈與閉包)
最后,希望也請(qǐng)各位看官大爺有錯(cuò)必究,不要只顧著噴小弟理解不到位的地方,[跪謝]。
如何理解redux中間件(二) 敬請(qǐng)期待