Redux和Koa的中介軟體機制相關原始碼都很精簡。
正文我將直接引用部分原始碼,並加以註釋來幫助我們更清晰的理解中介軟體機制。
Reudx
redux的中介軟體機制在原始碼中主要涉及兩個模組
內部的compose組合函式
'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提供的applyMiddleware函式
'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的原始碼(簡化)來做承接上文的簡要分析。
'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的中介軟體流程。
- 首先是提供一個compsoe函式用來生成一個由多箇中介軟體構成的組合函式(存在自呼叫能力)
- 將store的API注入中介軟體
- 將store.dispath作為引數傳遞給一個由compose函式構成的組合函式,返回一個包裝後的dispatch(也就是我們真實使用的dispatch)
- (或者是0.)構築一個特定結構的的中介軟體,第一層用於注入API,第二層用來接受上一個中介軟體返回的一個
接受action作為引數的函式
,並且自身同樣返回一個包含中介軟體具體操作的接受action作為引數的函式
。 - 由中介軟體提供的dispatch被呼叫,中介軟體被依次呼叫,如果遇到提供了非同步支援,那麼在非同步情況下,dispatch會先按照普通流程呼叫,當遇到redux-thunk或者redux-promise等函式時,會以同步的形式重新呼叫當前dispatch(中介軟體也會被重新呼叫一遍)
下面丟張費了九牛二虎之力畫的呼叫流程圖...隨意看看就好...
Koa2
感覺基本沒多少朋友看到這裡了吧...但我還是要寫完。 同上,先貼原始碼讓程式碼來告訴我們真相
在redux裡,中介軟體是作為一個附加的功能存在的,但在koa裡中介軟體是它最主要但機制。
koa的核心程式碼被分散在多個獨立的庫中,首先來看中介軟體機制核心的compose函式
'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本體
'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小結
關於koa2的中介軟體機制我並沒有解釋多少,主要是由於相比redux中介軟體來說簡明許多,另一個原因主要是懶,具體的執行流程圖,實際上同樣是洋蔥形的,只是store.dispatch被換成了最後一箇中介軟體而已。
末
本篇文章,雖然質量不行,大多註釋偏口語化(專業詞彙量不足),但還是希望能夠對一些同學有所幫助。
臨淵羨魚不如退而結網