Koa2和Redux中間件源碼研究

1、Koa2中間件源碼分析

在Koa2中,中間件被存放在一個數組中。 使用koa中,最多見的就是app.use(fn),use函數部分源碼以下所示。首先中間件必須是個函數。如果generator函數,則須要進行轉化。最後把該中間件推入middelaware數組中。javascript

constructor() {
    this.middleware = [];
}
use(fn) {
    this.middleware.push(fn);
}
複製代碼

當調用app.listen函數時,其實是建立了一個原生http服務器,並執行了Koa自身的callback方法。java

listen(...args) {
    const server = http.createServer(this.callback());
    return server.listen(...args);
}
複製代碼

callback函數中compose是將中間件封裝成一個迭代器,按照middleware數組中順序執行下去,實現了面向切面編程。handleRequest函數則是將req和res封裝成ctx,並執行中間件。編程

callback() {
   const fn = compose(this.middleware);
   const handleRequest = (req, res) => {
     const ctx = this.createContext(req, res);
     return this.handleRequest(ctx, fn);
   };
   return handleRequest;
}
handleRequest(ctx, fnMiddleware) {
   ...
   return fnMiddleware(ctx).then(handleResponse).catch(onerror);
}
複製代碼

compose用來返回一個迭代器函數fnMiddleware,該函數接受兩個參數,context和next。由於調用時只向fnMiddleware傳入了第一個參數context,因此next參數的值一直是undefined。數組

首先定義了index,dispatch傳入的參數 i 做爲每一箇中間件的調用標誌,每次中間件調用next函數都會讓index+1,如果中間件屢次調用next,則會使index大於等於該標誌,就會報錯。bash

而後依次讀取中間件數組中的中間件,並將context和封裝了調用標誌i的dispatch函數傳給中間件,就實現了中間件的過程。
當讀取完中間件數組後,即i === middleware.length時,將值爲是undefined的next傳給fn並結束迭代。dispatch函數全部的返回值都是Promise函數,這樣就能夠實現異步編程。服務器

function compose (middleware) {
  return function (context, next) {
    // last called middleware #
    let index = -1
    return dispatch(0)
    function dispatch (i) {
      if (i <= index) return Promise.reject(new Error('next() called multiple times'))
      index = i
      let fn = middleware[i]
      if (i === middleware.length) fn = next
      if (!fn) return Promise.resolve()
      try {
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}
複製代碼

2、Redux中間件源碼分析

在Redux中須要引入中間件的話,需引入applyMiddleware函數並將其做爲參數傳給createStore。下方enhancer就是就是applyMiddleware的返回值。app

export default function createStore(reducer, preloadedState, enhancer) {
    ...
    return enhancer(createStore)(reducer, preloadedState)
}
複製代碼

applyMiddleware接受一箇中間件數組爲參數。koa

export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    const store = createStore(...args)
    let dispatch = () => {
      throw new Error(
        `Dispatching while constructing your middleware is not allowed. ` +
          `Other middleware would not be applied to this dispatch.`
      )
    }
    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}
複製代碼

其中compose用Array.prototype.reduce將中間件數組封裝成一個層層包裹的函數。異步

function compose(...funcs) {
  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
複製代碼

若原本的中間件數組是異步編程

[a, b, c]

通過compose函數封裝後,就成了

(...arg) => a(b(c(...arg)))

dispatch = compose(...chain)(store.dispatch)
複製代碼

通過這個過程後,每次調用dispatch就會在中間件中進行遍歷。每一箇中間件都須要調用next(action)來保證中間件鏈都能被執行。

Redux中中間件的寫法通常爲

const middleware = store => next => action => {...}
複製代碼

根據上面的分析,store參數就是middlewareAPI,可是其中的dispatch並非真正的dispatch,這是爲了防止在中間件中調用store.dispatch而致使從新遍歷整個中間件鏈。next是下一個中間件,須要傳遞action,直到最後一箇中間件時,next便是原始的store.dispatch。

3、Koa2和Redux中間件比較

在二者中都出現了compose函數。 Koa2中的compose函數實現原理是用dispatch函數自身迭代,是從左向右執行中間件函數。而Redux中的compose實現原理是用了數組的reduce方法,從右向左執行中間件函數。二者執行順序雖然不一樣,同樣的是先執行的中間件能夠獲取後執行的中間件的狀態(store的狀態或者context的狀態),實現了面向切面編程。

相關文章
相關標籤/搜索