redux middleware 原始碼分析

牧云云發表於2019-03-01
redux middleware 原始碼分析

原文連結

middleware 的由來

在業務中需要列印每一個 action 資訊來除錯,又或者希望 dispatch 或 reducer 擁有非同步請求的功能。面對這些場景時,一個個修改 dispatch 或 reducer 程式碼有些乏力,我們需要一個可組合的、自由增減的外掛機制,Redux 借鑑了 Koa 中 middleware 的思想,利用它我們可以在前端應用中便捷地實現如日誌列印、非同步請求等功能。

redux middleware 原始碼分析

比如在專案中,進行了如下呼叫後,redux 就整合了 thunk 函式呼叫以及列印日誌的功能。

import thunk from `redux-thunk`
import logger from `../middleware/logger`
const enhancer = applyMiddleware(thunk, logger),  // 以 redux-thunk、logger 中介軟體為例介紹中介軟體的使用
const store = createStore(rootReducer, enhancer)
複製程式碼

下面追本溯源,來分析下原始碼。

applyMiddleware 呼叫入口

export default function createStore(reducer, preloadedState, enhancer) {
  // 通過下面程式碼可以發現,如果 createStore 傳入 2 個引數,第二個引數相當於就是 enhancer
  if (typeof preloadedState === `function` && typeof enhancer === `undefined`) {
    enhancer = preloadedState
    preloadedState = undefined
  }
  if (typeof enhancer !== `undefined`) {
    return enhancer(createStore)(reducer, preloadedState)
  }
  ...
}
複製程式碼

由上述 createStore 原始碼發現,applyMiddleware 會進行 applyMiddleware(thunk, logger)(createStore)(reducer, preloadedState) 的呼叫。

applyMiddleware 原始碼如下

export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    const store = createStore(...args)
    let dispatch = store.dispatch
    let chain = []

    const middlewareAPI = {
      getState: store.getState,                // 呼叫 redux 原生方法,獲取狀態
      dispatch: (...args) => dispatch(...args) // 呼叫 redux 原生 dispatch 方法
    }
    // 序列 middleware
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch // 返回加工過的 dispatch
    }
  }
}
複製程式碼

可以發現 applyMiddleware 的作用其實就是返回加工過的 dispatch,下面會著重分析 middlewares 是如何序列起來的以及 dispatch 是如何被加工的。

序列 middleware

const middlewareAPI = {
  getState: store.getState,
  dispatch: (...args) => dispatch(...args)
}
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
複製程式碼

觀察上述程式碼後發現每個 middleware 都會傳入引數 middlewareAPI,來看下中介軟體 logger 的原始碼 以及 redux-thunk 的原始碼, 發現中介軟體接受的第一個引數正是 ({ dispatch, getState })

// logger 原始碼
export default ({ dispatch, getState }) => next => action => {
  console.log(action)
  return next(action) // 經 compose 原始碼分析,此處 next 為 Store.dispatch
}
複製程式碼
// redux-thunk 原始碼
export default ({ dispatch, getState }) => next => action => {
  if (typeof action === `function`) {
    return action(dispatch)
  }
  return next(action) // 此處 next 為 logger 中介軟體返回的 (action) => {} 函式
}
複製程式碼

dispatch 是如何被加工的

接著上個小節,在 dispatch = compose(...chain)(store.dispatch) 中發現了 compose 函式,來看下 compose 的原始碼

export default function compose(...funcs) {
  // ...
  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
複製程式碼

compose 原始碼中的 funcs.reduce((a, b) => (...args) => a(b(...args))) 算是比較重要的一句,它的作用是返回組合引數後的函式,比如 compose(f, g, h) 等價於 (…args) => f(g(h(…args))),效果圖如下所示,呼叫 this.props.dispatch() 後,會呼叫相應的中介軟體,最終會呼叫 redux 原生的 store.dispatch(),並且可以看到中介軟體呼叫的形式類似資料結構中的棧(先進後出)。

redux middleware 原始碼分析

拿上個小節提到的 logger、redux-thunk 中介軟體為例,其 middleware 的內部序列呼叫方式如下,從而完成了 dispatch 功能的增強(支援如 this.props.dispatch(func) 的呼叫以及日誌功能)。具體可以看 專案中的運用

action => {
  if (typeof action === `function`) {
    return action(dispatch)
  }
  return (action => {
    console.log(action)
    return store.dispatch(action)
  })(action)
}
複製程式碼

參考文獻

深入React技術棧

相關文章