可以看到 middleware 的写法 ({dispatch, getState}) => (next) => (action) => action 是一种典型的柯里化函数,柯里化函数很适合做偏底层一些的函数抽象,方便再次封装时拿到任何一层的返回结果去做相应操作,随后也可选择是否再继续调用、何时调用。另外通过 middleware 结合 compose 的使用知道,柯里化函数很方便做函数组合。
最后,放出一张 redux-thunk 中间件源码截图,供大家检验。看 redux-thunk 又有一个问题需要解决:刚刚说 next 是上一个中间件返回的 dispatch,同时 middlewareAPI 中也有 dispatch。这两个在 redux-thunk 中都用到了,那区别是什么呢?下面代码技巧中解决这个问题。
代码技巧细心的同学可能发现了 middlewareAPI 中的 getState 就是 store.getState,而 dispatch 却是一个抛出异常的新函数!
另外, 为什么不直接将 dispatch 变量当成参数,而要再包一层函数呢?
其实 dispatch 的这两种定义方式的区别就在于赋值时机不同:
{ dispatch: dispatch } 这种方式定义,MiddlewareAPI.dispatch 被赋值的永远是抛了一段异常的 dispatch 变量。
{ dispatch: (...args) => dispatch(…args) } 这种方式定义,函数内的 dispatch 只有在执行 MiddlewareAPI.dispatch 时才会去找此时 dispatch 变量到底是哪个函数。第一次执行中间件操作时,在中间件内部使用的 dispatch 变量是那个只抛了异常的函数。第一次执行完毕后,由上图可以看到 dispatch 会被重新赋值,因此当用户调用从 redux-thunk 传出的 dispatch 时,已经是增强后的 dispatch 了。
多个中间件调用顺序直接看一个小 demo,执行顺序已用序号注释在后面,也可用断点看执行顺序。
通过 demo 可以看出,会先执行 f 中间件中的 fFn 再执行 g 中的 gFn。同时 gFn 的执行是需要在 f 中间件中调用 next(action) 才能执行到。如果你也想开发中间件,不要忘记这点。
compose在 applyMiddleware 中我们知道调用 compose(middleware1, middleware2, middleware3)(store.dispatch) 相当于调用 middleware1( middleware2( middleware3( store.dispatch ) ) )。compose 源码中只用了数组的原生方法 reduce 就优雅的解决了函数层层嵌套的问题(见下图)。
reduce 的基本用法可以参看 ,里面的小例子也很好。
下面为源码中 reduce 解决函数嵌套的运行原理,其中 m1,m2,m3,m4 为中间件。
这对我们平时写代码是个很好的启发,reduce 方法还很擅长做求和、去重、数组扁平化、数据分类等复杂操作,可以替代很多递归操作来实现功能(参见中的小例子)。下面将用多种方法实现异步回调,也可以实现依次执行动画的需求,一起来感受一下reduce写法的简介吧。
// 写法一:回调嵌套写法 function fn0() { // 此写法不易阅读且无法封装函数实现无限嵌套 console.log(0) setTimeout(function fn1() { console.log(1) setTimeout(function fn2(){ console.log(2) setTimeout(function fn3(){ console.log(3) }, 3000) }, 2000) }, 1000) } fn0() // 写法二:递归实现 function fn0(next) { console.log(0) setTimeout(next, 1000) } function fn1(next) { return () => { console.log(1) setTimeout(next, 2000) } } function fn2(next) { return ()=>{ console.log(2) setTimeout(next, 3000) } } function fn3(next) { return ()=>{ console.log(3) } } function recursiveReduce (...fns) { // 用递归实现源码中 compose 方法 if (fns.length < 2) { // 空数组或只有一个元素 return fns[0] } return recursiveReduce((...args) => fns[0](fns[1](...args)), ...fns.slice(2)) } recursiveReduce(fn0, fn1, fn2, fn3)() // 写法三:reduce 写法,其中 fn0, fn1, fn2, fn3 函数定义复用写法二的 function compose(...fns) { // 与源码中的 compose 方法一致 return fns.reduce((a, b) => (...args) => a(b(...args))) } compose(fn0, fn1, fn2, fn3)() // 写法四:自定义 next function fn0(next){ console.log(0) setTimeout(next, 1000) } function fn1(next){ console.log(1) setTimeout(next, 2000) } function fn2(next){ console.log(2) setTimeout(next, 3000) } function fn3(next){ console.log(3) } function nextFn(...fns) { // 借鉴 express 中间件实现方法 let i = 0; function next() { const fn = fns[i++]; if (!fn) return fn(next) } next() } nextFn(fn0, fn1, fn2, fn3)测试一下几种写法的运行速度(见下表),由快到慢依次是:回调嵌套写法、自定义 next、reduce 写法、递归实现。