这两天重新看下 Redux 的源码,这篇是阅读中间件源码的笔记。applyMiddleware 的源码中用到了一个 compose 方法
function compose(...funcs) {
if (funcs.length === 0) {
return args => args;
}
if (funcs.length === 1) {
return funcs[0];
}
return funcs.reduce((a, b) => (...args) => a(b(...args)));
}
这个 compose 方法也是 Redux 暴露出来的 api,但是看了文档之后,还是不怎么理解上面的 reduce 部分,干脆就自己写了个例子。
function test1(arg) {
console.log(arg);
console.log("test1");
}
function test2(arg) {
console.log(arg);
console.log("test2");
}
compose(
test1,
test2
)("arg");
// output:
// arg
// test2
// undefined
// test1
从输出可以看出,函数的执行顺序是从 compose 的最后一个函数到第一个函数。并且最后一个函数能接收到外部传进的参数。为什么能做到这样子呢?因为 reduce 里面,也就是 (a, b) => (...args) => a(b(...args) 这部分,返回的是一个函数,并不会立即执行。
明白了 compose 的用法之后,来看一个简略版的 applyMiddleware。
function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args);
const chain = middlewares.map(middleware =>
middleware({
getState: store.getState
})
);
const dispatch = compose(...chain)(store.dispatch);
return {
...store,
dispatch
};
};
}
一开始不理解这里为什么要用 compose 的返回值去覆盖原先的 dispatch。要回答这个问题,得先来看下 Redux 中间件的定义。以下面两个 log 中间件为例子
function logger1() {
return function next1(next) {
return function action1(action) {
console.log("logger1");
return next(action);
}
}
}
function logger2() {
return function next2(next) {
return function action2(action) {
console.log("logger2");
return next(action);
}
}
}
applyMiddleware(logger1, logger2)
当执行 const dispatch = compose(...chain)(store.dispatch) 的时候,store.dispatch 就是 next2 里面的 next 参数。而 action2 就是 next1 里面的 next 参数。新的 dispatch 实际就是 action1。
所以当 dispatch 一个 action 的时候,就会先执行 action1,然后是 action2,最后才是真正的 dispatch 函数。输出则是先输出 logger1,再输出 logger2。
(完)