Redux源码解读 - applyMiddleware篇
Redux
源码解读 - applyMiddleware
篇
applyMiddleware
🔗
先把源码贴出来:
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args)
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)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
刚开始我以为这函数这么短应该会很简单。
但是到后面和createStore
函数结合的时候我才发现比createStore
难多了。
主要是函数嵌套会有点晕(套,就嗯套)。
先说说这个东西的作用。
有些时候,我们想在每一次的状态变化之前,变化之后处理一些和业务无关的逻辑操作,比如写写日志。
那么我们可能会这样写。
const store = //...
// 某个业务
function fn() {
console.log(store.getState());
store.dispatch({type:TYPE});
console.log(store.getState());
}
fn();
但是这样写有一个问题,就是他耦合了业务函数fn
。
试想下如果我们在100
个业务函数中都这样写,有问题吗?
写当然没有问题,但是如果某一天要求在dispatch
前打印下时间,那就完蛋了,要改100
个地方,而且这100
个函数还分布在不同的文件里。
正所谓,复制黏贴一时爽,需求一变火葬场。
所以applyMiddleware
这个API就是为了解决这样的问题而出现的。
middleware
单词意思为中间件,简单点讲就是可以通过外部代码来增强内部的逻辑。
如果以我们的想法,可能会这么写:
const store = //...
const dispatch = store.dispatch;
store.dispatch = (action) => {
console.log(store.getState());
const newAction = store.dispatch(action);
console.log(store.getState());
return newAction;
}
store.dispatch({type:TYPE});
这就是增强dispatch
方法来实现额外的逻辑,不会耦合在业务里面。
而applyMiddleware
也是通过增强dispatch
来实现额外的逻辑。
不过applyMiddleware
支持多中间件,这个可以从它的参数看出来。
举个使用applyMiddleware
的小例子,就以状态改变前后输出日志为例。
const reducer = (state = 0, action) => {
switch (action.type) {
case "add":
return state + 1;
case "delete":
return state - 1;
default:
return state;
}
};
// 定义了一个打印日志的中间件
const loggerMiddleware = (middlewareAPI) => {
return (dispatch) => {
return (action) => {
console.log("logger before");
console.log("state is " + middlewareAPI.getState());
let newAction = dispatch(action);
console.log("logger before");
console.log("state is " + middlewareAPI.getState());
return newAction;
};
};
};
const store = createStore(
reducer,
// 用`applyMiddleware`包裹中间件和reducer一起传入createStore中
applyMiddleware(loggerMiddleware)
);
store.dispatch({
type: "add"
});
当我们执行上面的代码时,会打印:
当然也可以用两个中间件:
const reducer = (state = 0, action) => {
switch (action.type) {
case "add":
return state + 1;
case "delete":
return state - 1;
default:
return state;
}
};
// 定义了一个打印日志的中间件
const loggerMiddleware = (middlewareAPI) => {
return (dispatch) => {
return (action) => {
console.log("logger before");
console.log("state is " + middlewareAPI.getState());
let newAction = dispatch(action);
console.log("logger before");
console.log("state is " + middlewareAPI.getState());
return newAction;
};
};
};
// 定义了一个打印时间的中间件
const timeMiddleware = (middlewareAPI) => {
return (dispatch) => {
return (action) => {
console.log('time');
let newAction = dispatch(action);
return newAction;
};
};
};
const store = createStore(
reducer,
// 用`applyMiddleware`包裹中间件和reducer一起传入createStore中
applyMiddleware(loggerMiddleware, timeMiddleware)
);
store.dispatch({
type: "add"
});
运行之后就会出现:
ok,开始分析源码。
applyMiddleware
函数传入中间件的数组,然后直接返回了一个函数。
返回的函数传入了一个createStore
,也就是我们上篇帖子说的函数。
return createStore => (...args) => {
// 主体逻辑
}
源代码这样写可能看着晕,完全可以把它拆开。
return createStore => {
return (...arg) =>{
// 主体逻辑
}
}
主题的逻辑都在我上面标的位置。
const store = createStore(...args)
let dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
先是通过传入的createStore
创建函数,和第二层的arg
参数创建了一个store
。
定义了一个初始的dispatch
函数。
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
接下来定义了一个middlewareAPI
,对应我们创建中间件时最外层函数的参数
const chain = middlewares.map(middleware => middleware(middlewareAPI))
接着处理中间件数组,把中间件的API传进去,也就是解开了一层函数的包装
dispatch = compose(...chain)(store.dispatch)
这里我觉得就是这个函数最难理解的一段了。
逻辑上就是合并(compose
)已经给定API
的中间件数组,提供一个最原始的dispatch
,返回一个包装过的dispatch
。
可以先看一下compose
这个函数。
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
这段代码中最重要的就是最后一句funcs.reduce((a, b) => (...args) => a(b(...args)))
使用到reduce
这个API
的时候,我很喜欢举一个例子,就是累加:
const sum = [1, 2, 3].reduce((sum, val) => sum + val);
console.log(sum);
上面这段就会输出数字6
啦。
回到原来的函数,箭头函数如果套太深其实看着确实晕,所以把它拆出来。
funcs.reduce((a, b) => {
return (...args) => {
return a(b(...args))
}
})
这里我们用三个中间件来表示这一个过程。
PS:这里已经没有最外层的middlewareAPI
为参数的函数了,因为上一句的map
操作已经把这一层解开了。
const m1 = dispatch => {
return action => {
}
};
const m2 = dispatch => {
return action => {
}
};
const m3 = dispatch => {
return action => {
}
};
我们知道reduce
如果没有传入第二个参数作为默认值的话,是会选择数组的第一项作为初始值,然后跳过第一次循环。
所以在第二次循环的时候。
a
就为m1
,b
就为m2
,返回值作为下一次的a
。
第三次的循环的时候。
a
就为:
a = (...args) => {
return m1(m2(args))
}
b
为m3
,返回值作为下一次的a
。
那么最后返回的就是第四次的a
。
此时a
就为:
a = (...args) =>{
return ((...args) => {
return m1(m2(args))
})(m3(args))
}
m3(args)
返回了一个新的dispatch
。
然后依次是m2(args)
返回新的dispatch
。
最后是m1(args)
返回新的dispatch
。
最后m1
返回的dispatch
作为了新的store
的dispatch
。
也就是最后的代码:
return {
...store,
dispatch
}
而且可以从这个嵌套看出来,越后面的中间件他的执行距离真正的dispatch
会更近。
也就是我们之前使用了两个中间件loggerMiddleware
和timeMiddleware
。
传入的时候是先loggerMiddleware
后timeMiddleware
。
输出的时候就是先loggerMiddleware
后timeMiddleware
。
即
loggerMiddleware在dispatch之前的输出
timeMiddleware在dispatch之前的输出
真正的dispatch
timeMiddleware在dispatch之后的输出
loggerMiddleware在dispatch之后的输出
最后就是在createStore
中在传入了中间件的情况下直接返回。
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
// 传入了自己
return enhancer(createStore)(reducer, preloadedState)
}
这里需要注意一个点
就是MiddlewareAPI
中的dispatch
是整个store
的dispatch
。
而构造的中间件函数中的dispatch
参数是指包装了剩下中间件的dispatch
。
一般我们会把这个dispatch
写为next
,这样就更便于理解了。
const loggerMiddleware = (middlewareAPI) => {
return (next) => {
return (action) => {
console.log("logger before");
console.log("state is " + middlewareAPI.getState());
let newAction = next(action);
console.log("logger before");
console.log("state is " + middlewareAPI.getState());
return newAction;
};
};
};