Redux源碼解析系列(-) -- middleware 和 applyMiddleware

翻看Redux的源碼,能夠發現,它主要輸出createStore, combineReducers, bindActionCreators, applyMiddleware, compose 五個接口,
首先,讓咱們先看看middleware是個啥html

Redux裏咱們都知道dispatch一個action,就會到達reducer,而middleware就是容許咱們在dispatch action以後,到達reducer以前,搞點事情。react

clipboard.png

好比:打印,報錯,跟異步API通訊等等git

下面,讓咱們一步步來理解下middle是如何實現的:es6

step 1

假設咱們有個需求,想打印出dispatch的action以後,nextState的值。github

如圖:express

clipboard.png

先用一種很簡單的方式去實現redux

let action = toggleTodo('2');
console.log('dispatch', action);
store.dispatch(action);
console.log('next state', store.getState())

Step 2

包裹進函數裏

function dispatchAndLoge (store, action) {
    let action = toggleTodo('2');
    console.log('dispatch', action);
    store.dispatch(action);
    console.log('next state', store.getState())
}

可是咱們並不想每次要用的時候都須要import這個函數進來,因此咱們須要直接替代。數組

Step 3

直接替代store的dispatch,在原先的dispatch先後加上咱們的代碼,把原先的store.dispatch放進next裏。app

const next = store.dispatch;
store.dispatch = function (action) {
   let action = toggleTodo('2');
   console.log('dispatch', action);
   next(action)
   console.log('next state', store.getState())
}

這樣下一次咱們在用dispatch的時候就自動附帶了log的功能,這裏的每個功能,咱們都稱之爲一個middleware,用來加強dispatch的做用。異步

接下來咱們就須要思考,如何能夠鏈接多個middleware。

先把報錯的middleware包裝成函數寫上來

function patchStoreToAddLogging(store) {
  let next = store.dispatch;
  store.dispatch = function dispatchAndLog(action) {
    console.log('dispatching', action);
    let result = next(action);
    console.log('next state', store.getState());
    return result;
  }
}

function patchStoreToAddCrashReporting(store) {
  let next = store.dispatch;
  store.dispatch = function dispatchAndReportErrors(action) {
    try {
      return next(action);
    } catch (err) {
      console.error('Caught an exception!', err);
      Raven.captureException(err, {
        extra: {
          action,
          state: store.getState();
        }
      })
      throw err;
    }
  }
}

這裏咱們用替代了store.dispatch,其實咱們能夠直接return 這個函數,就能夠在後面實現一個鏈式調用,賦值這件事就在applyMiddleware裏作。

step 4

function logger(store) {
  let next = store.dispatch

  // Previously:
  // store.dispatch = function dispatchAndLog(action) {

  return function dispatchAndLog(action) {
    console.log('dispatching', action)
    let result = next(action)
    console.log('next state', store.getState())
    return result
  }
}

這裏咱們提供了一個applyMiddleware的方法,能夠將這兩個middleware連起來
它主要作一件事:

將上一次返回的函數賦值給store.dispatch

function applyMiddlewareByMonkeypatching(store, middlewares) {
  middlewares = middlewares.slice()
  middlewares.reverse()

  // Transform dispatch function with each middleware.
  middlewares.forEach(middleware =>
    // 因爲每次middle會直接返回返回函數,而後在這裏賦值給store.dispatch,
    、、  下一個middle在一開始的時候,就能夠經過store.dispatch拿到上一個dispatch函數
    store.dispatch = middleware(store)
  )
}

接下來,咱們就能夠這樣用

applyMiddlewareByMonkeypatching(store, [logger, crashReporter])

剛剛說過,在applyMiddle裏必需要給store.dispatch賦值,不然下一個middleware就拿不到最新的dispatch。

可是有別的方式,那就是在middleware裏不直接從store.dipatch裏讀取next函數,而是將next做爲一個參數傳入,在applyMiddleware裏用的時候把這個參數傳下去。

step 5

function logger(store) {
  return function wrapDispatchToAddLogging(next) {
    return function dispatchAndLog(action) {
      console.log('dispatching', action)
      let result = next(action)
      console.log('next state', store.getState())
      return result
    }
  }
}

用es6的柯西化寫法,能夠寫成下面的形式,其實這個next我我的以爲叫previous更爲合適,由於它指代的是上一個store.dispatch函數。

const logger = store => next => action => {
  console.log('dispatching', action)
  let result = next(action)
  console.log('next state', store.getState())
  return result
}

const crashReporter = store => next => action => {
  try {
    return next(action)
  } catch (err) {
    console.error('Caught an exception!', err)
    Raven.captureException(err, {
      extra: {
        action,
        state: store.getState()
      }
    })
    throw err
  }
}

由此咱們能夠看到因此middleware要傳入的參數就是三個,store,next,action。
接下來,再看看redux-thunk 的源碼,咱們知道,它用於異步API,由於異步API action creator返回的是一個funciton,而不是一個對象,因此redux-thunk作的事情其實很簡單,就是看第三個參數action是不是function,是的話,就執行它,若是不是,就按照原來那樣執行next(action)

function createThunkMiddleware(extraArgument) {
 return (store) => next => action => {
   if(typeof action === 'function') {
      return action(dispatch, getState, extraArgument)
   }
   return next(action)
 }

}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

step 6

接着,在applyMiddleware裏有能夠不用馬上對store.dispatch賦值啦,能夠直接賦值給一個變量dispatch,做爲middleware的參數傳遞下去,這樣就能鏈式的加強dispatch的功能啦~

function applyMiddleware(store, middlewares) {
  middlewares = middlewares.slice();
  middlewares.reverse();
  let dispatch = store.dispatch;
  middlewares.forEach(middleware => {
    dispatch = middleware(store)(dispatch)
  })
  return Object.assign({}, store, {dispatch})
}

下面終於能夠看看applyMiddleware的樣子啦

/**
 * Composes single-argument functions from right to left. The rightmost
 * function can take multiple arguments as it provides the signature for
 * the resulting composite function.
 *
 * @param {...Function} funcs The functions to compose.
 * @returns {Function} A function obtained by composing the argument functions
 * from right to left. For example, compose(f, g, h) is identical to doing
 * (...args) => f(g(h(...args))).
 */

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

//能夠看出compose作的事情就是上一個函數的返回結果做爲下一個函數的參數傳入。

/**
 * Creates a store enhancer that applies middleware to the dispatch method
 * of the Redux store. This is handy for a variety of tasks, such as expressing
 * asynchronous actions in a concise manner, or logging every action payload.
 *
 * See `redux-thunk` package as an example of the Redux middleware.
 *
 * Because middleware is potentially asynchronous, this should be the first
 * store enhancer in the composition chain.
 *
 * Note that each middleware will be given the `dispatch` and `getState` functions
 * as named arguments.
 *
 * @param {...Function} middlewares The middleware chain to be applied.
 * @returns {Function} A store enhancer applying the middleware.
 */
export default function applyMiddleware(...middlewares) {
  //能夠這麼作是由於在creatStore裏,當發現enhancer是一個函數的時候
  // 會直接return enhancer(createStore)(reducer, preloadedState)
   
  return (createStore) => (...args) => {
    // 以後就在這裏先創建一個store
    const store = createStore(...args)
    let dispatch = store.dispatch
    let chain = []
    // 將getState 跟dispatch函數暴露出去
    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
    //這邊返回chain的一個數組,裏面裝的是wrapDispatchToAddLogging那一層,至關於先給
     middle剝了一層皮,也就是說
    // 接下來只須要開始傳入dispatch就行
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    
    dispatch = compose(...chain)(store.dispatch)
    // wrapCrashReport(wrapDispatchToAddLogging(store.dispatch))
    // 此時返回了上一個dispatch的函數做爲wrapCrashReport的next參數
    // wrapCrashReport(dispatchAndLog)
    // 最後返回最終的dipatch
    return {
      ...store,
      dispatch
    }
  }
}

總結,其實每個middleware都在加強dispatch的功能,在dispatch action的先後搞點事情~

參考文檔:
http://redux.js.org/docs/adva...
https://github.com/reactjs/re...
https://github.com/reactjs/re...
https://github.com/reactjs/re...

相關文章
相關標籤/搜索