redux middleware 源碼分析

原文連接前端

middleware 的由來

在業務中須要打印每個 action 信息來調試,又或者但願 dispatch 或 reducer 擁有異步請求的功能。面對這些場景時,一個個修改 dispatch 或 reducer 代碼有些乏力,咱們須要一個可組合的、自由增減的插件機制,Redux 借鑑了 Koa 中 middleware 的思想,利用它咱們能夠在前端應用中便捷地實現如日誌打印、異步請求等功能。react

好比在項目中,進行了以下調用後,redux 就集成了 thunk 函數調用以及打印日誌的功能。git

import thunk from 'redux-thunk'
import logger from '../middleware/logger'
const enhancer = applyMiddleware(thunk, logger),  // 以 redux-thunk、logger 中間件爲例介紹中間件的使用
const store = createStore(rootReducer, enhancer)
複製代碼

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

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) 的調用。redux

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 是如何被加工的。app

串行 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(),而且能夠看到中間件調用的形式相似數據結構中的棧(先進後出)。源碼分析

拿上個小節提到的 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技術棧

相關文章
相關標籤/搜索