Redux和Koa的中間件機制相關源碼都很精簡。redux
正文我將直接引用部分源碼,並加以註釋來幫助咱們更清晰的理解中間件機制。api
redux的中間件機制在源碼中主要涉及兩個模塊數組
'redux/src/compose.js'
//compose組合函數,接收一組函數參數返回一個組合函數
//須要提早注意的一點是,funcs數組內的函數基本上(被注入了api)就是咱們在將來添加的中間件如logger,thunk`等
export default function compose(...funcs) {
//爲了保證輸出的一致性,始終返回一個函數
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
//這一步可能有些抽象,但代碼極其精緻,經過歸約函數處理數組,最終返回一個逐層自調用的組合函數。
//例: compose(f, g, h) 返回 (...args) => f(g(h(...args))).
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
//或許是版本更新的緣故,相比以前看到過的compose要精簡了不少,尤爲是在最終的規約函數處理上,高大上了很多。
//由本來的reduce來依次執行中間件進化爲函數自調用,更加的【函數式】。。下面順便貼出多是舊的compose函數,你們自行對比。
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
const last = funcs[funcs.length - 1]
const rest = funcs.slice(0, -1)
return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
}
複製代碼
'redux/src/applyMiddleware.js'
//能夠看到compose函數被做爲🔧引入了
import compose from './compose'
//暴露給開發者,用來傳入中間件
export default function applyMiddleware(...middlewares) {
//createStore函數的控制權將被轉讓給applyMiddleware,因爲本篇主要談中間件,就不擴展來解釋了
return createStore => (...args) => {
//------------------------------------------------------------------非中間件相關,一些上下文環境的代碼-----------------------
//初始化store,此處的...args實際爲reducer, preloadedState(可選)
const store = createStore(...args)
//聲明一個零時的dispatch函數,注意這裏的let,它將在構建完畢後被替換
let dispatch = () => {
throw new Error('dispatch不容許在構建中間件的時候被調用,其實主要是爲了防止用戶自定義的中間件在初始化的時候調用dispatch。 在下文的示例中能夠看到, 而且普通的同步的中間件通常是用不到dispatch的')
}
//提供給中間件函數的api,能夠看到dispatch函數在這裏經過函數來'動態的調用當前環境下的dispatch'
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
//爲中間件注入api
const chain = middlewares.map(middleware => middleware(middlewareAPI))
//------------------------------------------------------------------------------------------------------------------------
'關鍵'
//列出上文的例子可能比較直觀: 調用compose(f, g, h)返回(...args) => f(g(h(...args))).
//1.調用compose函數,返回一個由多個/一箇中間件構成的組合函數
//2.將store.dispatch做爲參數傳入組合函數中用來返回一個新的/包裝過的dispatch函數
//'注意:這部分須要聯繫下文中的中間件源碼來對照着進行理解,因此讓咱們暫時把這裏加入腦內緩存'
dispatch = compose(...chain)(store.dispatch)
//返回一個store對象,在添加了中間件的狀況下,咱們實際最終獲取的store就是從這裏拿到的。
return {
...store,
dispatch
}
}
}
複製代碼
原本不想寫這麼長來着,但但願更多你們可以更簡單的理解,就多貼了些源碼,畢竟代碼遠比文字更好理解,下面我用logger和thunk的源碼(簡化)來作承接上文的簡要分析。promise
'redux-logger'
//因爲logger源碼看起來好像有點複雜(懶得看),我就簡單實現了...不嚴謹請輕噴
一般來講redux的中間件主要分爲兩層。
//第一層,用於接受store提供的API,在傳給構建中間件以前就會被調用。
const logger = ({getState}) => {
// 第二層,利用了函數(currying)柯理化將計算/運行延遲,請讓我用更多的註釋來幫咱們理清思路...
// 仍是先列出上文的例子比較直觀【手動滑稽】:)
// 例:compose(f, g, h)返回(...args) => f(g(h(...args))).
// 關聯上文:dispatch = compose(...chain)(store.dispatch) 代入例子 ((dispatch) => f(g(h(dispatch))(store.dispatch)
// 能夠看到清晰看到,中間件被自右向左執行,store.dispatch做爲參數被傳入給最早執行(最右側)的中間件
// 中間件的第二層被執行,返回一個'接受action做爲參數的函數',這個函數做爲調用下一個(本身左側)的中間件,依次執行至最左側,最終返回的一樣是一個'接受action的函數'
// 最終咱們調用的dispatch實際上就是這個被最終返回的函數
// '咱們的真實流程是 dispatch(包裝過的) => 中間件1 => 中間件2 => dispatch(store提供的) => 中間件2 => 中間件1 => 賦值(若是有返回的話)'
// 果真仍是沒有解釋清楚,請拋開個人註釋,多看幾遍代碼
return next => action => {
console.log('action', action)
console.log('pre state', getState())
//next實質就是下一個(右側)中間件返回的閉包函數/當前中間件若是是最後一個或者惟一的,那麼next就是store提供的dispatch
//next(action)函數調用棧繼續往下走,也就是調用下一個(右側)中間件,nextVal會接受返回的結果
const nextVal = next(action)
console.log('next state', getState())
//將結果返回給上一個中間件(左側)或者是開發者(第一個中間件的狀況下)
return nextVal
}
}
'redux-thunk'
//這個是官方的源碼,異常精簡,這個函數支持了dispatch的異步操做,讓咱們來看看如何實現的。
//這裏就不復述上面的註釋了,只解釋下關於異步的支持。
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
//將dispatch函數的執行權限轉移給開發者,咱們一般在異步結束以後調用dispatch(此時是同步的)。
//注意:在這裏咱們本來的中間件執行流程被中斷,並從新以同步的模式執行了一遍,'因此redux-thunk在中間件中的位置將會對其他中間件形成影響,例如logger中間件被執行了兩次什麼的...'
// 另外一個要注意的是,這裏的dispatch函數其實是在構築中間件後被包裝的函數。
return action(dispatch, getState, extraArgument);
}
//dispatch同步時,直接將控制權轉讓給下一個中間件。
//dispatch異步時,在異步結束後調用的dispatch中,一樣將控制權轉讓給下一個中間件。
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
複製代碼
最後讓咱們梳理總結一下Redux的中間件流程。緩存
接受action做爲參數的函數
,而且自身一樣返回一個包含中間件具體操做的接受action做爲參數的函數
。下面丟張費了九牛二虎之力畫的調用流程圖...隨意看看就好...bash
感受基本沒多少朋友看到這裏了吧...但我仍是要寫完。 同上,先貼源碼讓代碼來告訴咱們真相閉包
在redux裏,中間件是做爲一個附加的功能存在的,但在koa裏中間件是它最主要但機制。app
'koa-compose'
//注意:'在函數中始終返回Promise,是因爲koa2採用了async await語法糖形式'
//接受一箇中間件數組
function compose (middleware) {
返回一個處理函數,在Request請求的最後被調用,並傳入請求的相關參數
return function (context, next) {
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
//中間件被執行完畢了,直接返回一個Promise
if (!fn) return Promise.resolve()
try {
//將下一個中間件的函數調用包裹在next中,返回給當前的中間件
return Promise.resolve(fn(context, function next () {
return dispatch(i + 1)
}))
} catch (err) {
//監聽錯誤,並拋出
return Promise.reject(err)
}
}
}
}
//相比較redux的中間件缺乏了幾分函數式的精緻,但我依舊寫不出相似精簡的代碼.jpg
複製代碼
'koa/lib/application.js'
'104-115行的use函數(簡化)'
//這是koa暴露給咱們的use函數,相信大多數同窗都不陌生
use(fn) {
//很是明瞭,就是將中間件添加入middleware數組
this.middleware.push(fn);
return this;
}
'125-136行的callback函數'
//callback函數將在koa.listen中被調用具體請自行查看源碼
callback() {
//調用compsoe
const fn = compose(this.middleware);
if (!this.listeners('error').length) this.on('error', this.onerror);
const handleRequest = (req, res) => {
const ctx = this.createContext(req, res);
//Request時被調用
return this.handleRequest(ctx, fn);
};
return handleRequest;
}
複製代碼
關於koa2的中間件機制我並無解釋多少,主要是因爲相比redux中間件來講簡明許多,另外一個緣由主要是懶,具體的執行流程圖,實際上一樣是洋蔥形的,只是store.dispatch被換成了最後一箇中間件而已。koa
本篇文章,雖然質量不行,大多註釋偏口語化(專業詞彙量不足),但仍是但願可以對一些同窗有所幫助。異步
臨淵羨魚不如退而結網