redux middleware 是 redux 的一个 advanced feature. 这个概念并不是很新奇,以为在 Koa 里面早已经实现过了. 对比与原生的redux middleware , koa 的 middleware 差不多相当于是爸爸级的 level 了. 这么说,是有依据的. 我们这里,来深入一下源码,具体看一下redux middleware 到底做了些啥.
我们首先来探讨一下基本的源码吧.
redux 的源码可以说是比较简单的。 首先,入口文件是 index.js 。我们来看一下,里面具体的内容:
// 从不同的文件里面导出相关方法
export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose
}
这里,其实是 redux 的提供的所有方法了. createStore,combineReducers,bindActionCreators 这三个方法,与 middleware 关系不大,我这里就不赘述了. 这里,主要讲解一下 applyMiddleware 方法和 compose 方法.
in fact, compose 是一个非常基础的方法, 用来以函数式的编程来组合中间件, 在 koa 中我们也同样遇见过这样的写法. applyMiddleware 也是用到这样的方法的. so, 我们来具体看看.
compose 方法
compose 的源码就是一个函数 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];
// 获取除最后一个以外的函数[0,length-1)
const rest = funcs.slice(0, -1)
// 通过函数 curry 化
return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
}
第一眼看的时候, 肯定超级S B。。。 md... 这写个啥... 看了一下官方给的注释就是:
// 简单 demo 就是
compose(f, g, h) is identical to doing (...args) => f(g(h(...args))).
合着就是个函数嵌套的写法。。。
关键坑爹的在于他的reduceRight方法, 写的真心6. 由于,return 两个函数时,只会返回第二个执行的结果:
function test(a,b){
return a(),b();
}
console.log(test(a=>1,b=>2));
// 开玩笑的. 上面那种只是科普一下. 他真正的机制实际上是利用 reduceRight 的第二个参数来执行的
Array.reduceRight(fn,start);
// 主要还是这里的start, 相当于就是 last(...args)
// 将上面代码翻译一下就是
rest.reduceRight(function(composed, f){return f(composed)}, last(...args));
//... 慢慢去体会吧...
所以, 一开始看的时候,在纠结 最后一个 composed 都没执行... 后来发现, 原来还有一层 last(...args).
不过实话说, 真心没有 koa 里面的 compose 函数写得好, 你直接先写一个 noop 函数不行吗!!!
// 俺 实际写了一个替换的compose. 感觉这个看的清楚一点
function compose(...funcs) {
return function(...args) {
var len = funcs.length,
middle = funcs[--len](...args);
while (len--) {
middle = funcs[len].call(this, middle);
}
return middle;
}
}
// 测试
console.log(compose(a => a, b => b, (c,b) => c+b)('a', 'b'));
这个是简单的compose 函数. 下面,我们来看一下重点,关于 redux-middleware 的核心方法, applyMiddleware.
applyMiddleware 中间件
由于这个中间件有点复杂, 对传入的函数有具体的要求. 我们先来看一下使用该方法的上下文:
直接看 offical website 找到一个 demo:
let store = createStore(
todoApp,
// applyMiddleware() tells createStore() how to handle middleware
applyMiddleware(logger, crashReporter)
)
最终 applyMiddleware return 的结果,还需要返回到 createStore 里去的. 通过 createStore 传入方法时, 函数里面并未对 里面做什么处理.
function createStore(reducer, preloadedState, enhancer) {
// 这里就是一些列条件判断, 如果你使用 middle 是上面的形式,那么就会直接将参数赋给 enhancer
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
// 实际上调用 applyMiddleware 方法的地方. 注意他传入的参数即可. z
return enhancer(createStore)(reducer, preloadedState)
}
}
ok, 我们了解了传入 applyMiddleware 的参数后, 继续看. 中间件的写法:
// 这里就看一下logger 就Ok
const logger = store => next => action => {
// debug info
console.group(action.type)
console.info('dispatching', action)
let result = next(action)
// debug info
console.log('next state', store.getState())
console.groupEnd(action.type)
return result
}
// 我们将 debug 信息去掉之后,可以得到一个精简版的 middleware
const logger = store => next => action => {
// 传递前, 执行的代码
let result = next(action)
// 传递完, 执行的代码
return result
}
看到这里,有种 koa 的感觉. next(action) 显示的将控制权交给下一个函数进行执行. 相当于就是 onion model.
这里, 放一下 applyMiddleware 的源码:
export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer, preloadedState, enhancer) => {
var store = createStore(reducer, preloadedState, enhancer)
var dispatch = store.dispatch
var chain = []
var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
看到这里, 我也是有点惊讶, 第一是他的中间件传递的复杂度巧妙的转化为箭头函数的写法, 第二是他显示的改变了真正的dispatch的内容。
最后实际调用整个流程,是直接根据applyMiddleware提供的方法来的:
// 注意这里是 applyMiddleware 提供的 dispatch 方法
store.dispatch(action)
如果按照上面的调用方式写的话,具体调用顺序就是:
applyMiddleware(logger, crashReporter)
applyMiddleware all procedure
applyMiddleware 整个执行过程:
对应于上文, 整个API的流程图为:
关键点在于applyMiddleware 和 中间件两个内容.
关于 redux-middleware 还有一个比较流行的库, 即, redux-thunk . 该库灰常简单, 就一个函数.
redux-thunk
直接看源码算了:
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
他和原来的中间件的写法有一个非常不同的地方,在于. 他写中间件的地方, 不在 createStore 里面, 而在 dispatch 里面.
// 初始化调用
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
// thunk 类型的中间件
function doSth(forPerson) {
// 这里必须返回一个函数... 才能达到中间件的效果
return function (dispatch) {
return async().then(
sauce => dispatch(makeASandwich(forPerson, sauce)),
error => dispatch(apologize('The Sandwich Shop', forPerson, error))
);
};
}
// 更简便的写法可以为:
let doSth = forPerson=>dispatch=> async().then(
sauce => dispatch(makeASandwich(forPerson, sauce)),
error => dispatch(apologize('The Sandwich Shop', forPerson, error))
)
看源码,我们可以很容易发现, 在实际调用 dispatch 时, 不仅仅只有 dispatch 这一个参数,还有 getState,extraArgument 这两个参数。 so, what are they doing?
getState 这个就不用说了, 就是用来获取当前 redux 的 state.
那 extraArgument 干啥嘞?
看源码很容易发现, 就是在初始化 thunk 时, 传入的参数. 其实, 也不会经常用到. 所以, 我们中间件实际可以写为:
let doSth = forPerson=>(dispatch,getState,extArgs)=> async().then(
sauce => dispatch(makeASandwich(forPerson, sauce)),
error => dispatch(apologize('The Sandwich Shop', forPerson, error))
)
对比与上面的 applyMiddle 来说, 使用 redux-thunk 可以实现 私有, 定制化的 中间件操作. 而,原来的 applyMiddleware 则 只能在初始阶段设置相关的中间件, 但 却可以实现 next 的执行域的分离. 所以, 两个都可以使用, 只是看你具体需求是啥.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。