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
本篇文章,虽然质量不行,大多注释偏口语化(专业词汇量不足),但仍是但愿可以对一些同窗有所帮助。异步
临渊羡鱼不如退而结网