如何理解redux中間件(一)

“中間件”這個(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

其中storedispatchgetState方法是在createStore時(shí)通過applyMiddleware注入的。

next 方法用于維護(hù)中間件調(diào)用鏈和dispatch,它返回一個(gè)接受 action 對(duì)象的柯里化函數(shù),接受的 action 對(duì)象可以在中間件中被修改,再傳遞給下一個(gè)被調(diào)用的中間件,最終dispatch會(huì)使用中間件修改后的action來執(zhí)行。

如果得到的action是個(gè)函數(shù),就用 dispatchgetState 當(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的方法或者dispatchotherAction,所以我們看到例子中的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)期待

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容