“ 很早之前就读了中间件相关的源码,最近终于找了一个时间来更新,今天涉及到的内容也是很精彩的,对中间件源码感兴趣的小伙伴也不要错过哟。”
在我工作项目中,涉及到了redux中间件的实现,在这里我就截取少部分代码给大家介绍源码解析,代码如下所示:
1 2 3 4 5
| import { createStore, applyMiddleware, compose } from 'redux'; import thunk from 'redux-thunk'; const createStoreWithMiddleware = compose( applyMiddleware(thunk) )(createStore);
|
1. 函数式编程
其实Redux的源码没有多少,但是在讲解之前我需要先给大家说一下什么是函数式编程?其实可以简单的理解为:复杂的问题可以细分为一个个的更小的函数,然后利用组合函数来进行解决。
在讲解完什么是函数式编程之后,当然接着就少不了什么是高阶函数?在js中一切皆对象,并且在js中函数是一等公民,那么使得可以接受函数作为参数,这种函数就称为高阶函数。
2. compose函数源码解析
compose函数是十分重要的,它的功能其实就是:从右到左,组合函数。场景使用有许多,比如在本文即将介绍的redux,还有就是我们经常使用到的webpack中loader执行。其实不要被compose这个函数给吓到了,它的实现核心其实是采用了reduce方法,然后将所有函数进行一个串联。reduce方法我就不过多介绍了,不懂的同学可以去看一下我之前写过关于reduce函数扩展的文章,或者去mdn文档中进行一个查看。
compose函数源码如下所示,有没有感觉到很简单,其实就是最后那个return语句中的reduce方法是精髓,如果你对reduce很熟悉的话,那么恭喜你对compose就理解了。当然你要是觉得reduce的使用是你的痛点的话,也不要慌,我这就一一道来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| export default function compose(...funcs) { // funcs数组是空数组返回一个箭头函数 if (funcs.length === 0) { return arg => arg } // funcs数组中只有一个值,返回该值 if (funcs.length === 1) { return funcs[0] } // 其实最后返回的就是(...args) => a(b(...args)),至于reduce的执行重点看a(b(...args)) /** * 我举一个例子: * funcs=[fn1, fn2, fn3, fn4],reduce执行顺序: * (1) a1 = (...args) => fn1(fn2(...args)); * (2) a2 = (...args) => a1(fn3(...args)); * (3) a3 = (...args) => a2(fn4(...args)); * 将(1)(2)带入到(3)得到: * a3 = (...args) => fn1(fn2(fn3(fn4(...args)))) * / return funcs.reduce((a, b) => (...args) => a(b(...args))) }
|
compose函数执行顺序之后,返回值是另外一个函数;它的作用就是:从数组从后往前按照顺序执行,然后把前一个执行函数的返回值作为下一个执行函数的入参。关于这句话如果你觉得很绕的话,可以把它想象成洋葱圈模型,从内到外依次调用。下面我画的这幅图可以帮助你进行一个理解,图形如下所示:

洋葱模型执行由内到外图
3. 柯里化函数
在讲到redux的中间件源码时,我们还需要了解一样常用的知识点,什么是柯里化函数?把接受多个参数的函数变化成接受一个单一参数的函数,返回接受余下的参数并且返回结果的新函数。可能有点绕,我来举个例子你就知道什么是柯里化了。
就举一个最常见的求和例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| // 非柯里化实现 function sum (a, b, c) { return a + b + c; }
// 柯里化实现 function sum (a) { return function (b) { return function (c) { return a + b + c; } } }
// 对上面柯里化函数进行一个简化(请记住这种写法,在中间件源码中会使用到) const sum = a => b => c => a+b+c;
|
4. applyMiddleware源码解析
接下来我们就来揭开redux中间件神秘的面纱,其实中间件的源码很少,可能就20多行左右,其实中间件的核心是:改造dispatch方法,具体代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| export default function applyMiddleware(...middlewares) { return createStore => (...args) => { const store = createStore(...args) // 在这里dispatch方法还不能使用,因为还没改造成next方法 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) } // 将中间件函数先遍历,执行里面的方法并返回,得到一个dispatch方法的中间件数组 const chain = middlewares.map(middleware => middleware(middlewareAPI)) /** * 这里用到了我们在上面提及的compose方法,compose方法会将 * 包含了dispatch方法的中间件数组组合成一个嵌套的包装函数返回 */ dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }
|
是不是感觉其实也不是想象中那么难,把它简化以下就变成如下所示代码:
1 2 3 4 5 6 7 8 9 10 11
| // 其实applyMiddleware也是一个柯里化函数 function applyMiddleware(...middlewares) { // middlewares表示中间件d额数组 return function (createStore) { // createStore是用于创建store的函数 return function (...args) { // args其实是reducer return { ...store, // createStore(reducer) dispatch // 改造后的dispatch } } } }
|
关于这里为什么要传next?可以保证中间件不断裂。比如下面我们要介绍的redux-thunk中会有意外中断情况。
next函数会不断往下传递,如果你不传递next函数就会中断传递。因此当我们最后调用store.dispatch(store)时中断了dispatch的传递,因为我们只调用了store.dispatch方法并没有执行next向下传递,因此最后在这里结束了。
注意:如果我们要写自己的中间件的话,那么我们需要定义成如下所示的形式(柯里化函数):
const middleware = store => next => action => next(action);
5. redux-thunk源码
在介绍了上面中间件的源码之后,下面我们就来看一个常见的redux-thunk中间件,它的作用主要是:因为redux默认dispatch只能接受一个对象参数,而像函数或者promise它都是不允许的,此时就有了redux-thunk中间件的由来,它让dispatch可以是对象也可以是函数,这样就可以处理异步代码。接下来我们就来看看它的源码。
1 2 3 4 5 6 7 8
| function createThunkMiddleware(extraArgument) { return ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState, extraArgument); } return next(action); }; }
|
有没有很眼熟,其实它的格式就是上面我们所要求的柯里化函数格式。其是redux-thunk源码很简单,就是判断传递的action是否是函数,是函数就执行action本身并将dispatch和getState传入,否则就继续执行next跳转到下一个中间件中。其实就是强化了dispatch让其可以执行函数的作用。
如果你对本章有了一定的了解话,可以来个三连哟。在后续公众号中我会来讲解react-redux中connect的源码,欢迎大家来看哟。