Redux源码解读 - applyMiddleware篇

Redux源码解读 - applyMiddleware

applyMiddleware

先把源码贴出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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难多了。

主要是函数嵌套会有点晕(套,就嗯套)。

先说说这个东西的作用。

有些时候,我们想在每一次的状态变化之前,变化之后处理一些和业务无关的逻辑操作,比如写写日志。

那么我们可能会这样写。

1
2
3
4
5
6
7
8
9
10
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单词意思为中间件,简单点讲就是可以通过外部代码来增强内部的逻辑。

如果以我们的想法,可能会这么写:

1
2
3
4
5
6
7
8
9
10
11
12
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的小例子,就以状态改变前后输出日志为例。

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
28
29
30
31
32
33
34
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"
});

当我们执行上面的代码时,会打印:

当然也可以用两个中间件:

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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,也就是我们上篇帖子说的函数。

1
2
3
return createStore => (...args) => { 
// 主体逻辑
}

源代码这样写可能看着晕,完全可以把它拆开。

1
2
3
4
5
return createStore => {
return (...arg) =>{
// 主体逻辑
}
}

主题的逻辑都在我上面标的位置。

1
2
3
4
5
6
7
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函数。

1
2
3
4
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}

接下来定义了一个middlewareAPI,对应我们创建中间件时最外层函数的参数

1
const chain = middlewares.map(middleware => middleware(middlewareAPI))

接着处理中间件数组,把中间件的API传进去,也就是解开了一层函数的包装

1
dispatch = compose(...chain)(store.dispatch)

这里我觉得就是这个函数最难理解的一段了。

逻辑上就是合并(compose)已经给定API的中间件数组,提供一个最原始的dispatch,返回一个包装过的dispatch

可以先看一下compose这个函数。

1
2
3
4
5
6
7
8
9
10
11
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的时候,我很喜欢举一个例子,就是累加:

1
2
const sum = [1, 2, 3].reduce((sum, val) => sum + val);
console.log(sum);

上面这段就会输出数字6啦。

回到原来的函数,箭头函数如果套太深其实看着确实晕,所以把它拆出来。

1
2
3
4
5
funcs.reduce((a, b) => {
return (...args) => {
return a(b(...args))
}
})

这里我们用三个中间件来表示这一个过程。

PS:这里已经没有最外层的middlewareAPI为参数的函数了,因为上一句的map操作已经把这一层解开了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const m1 = dispatch => {
return action => {
}
};

const m2 = dispatch => {
return action => {
}
};

const m3 = dispatch => {
return action => {
}
};

我们知道reduce如果没有传入第二个参数作为默认值的话,是会选择数组的第一项作为初始值,然后跳过第一次循环。

所以在第二次循环的时候。

a就为m1b就为m2,返回值作为下一次的a

第三次的循环的时候。

a就为:

1
2
3
a = (...args) => {
return m1(m2(args))
}

bm3,返回值作为下一次的a

那么最后返回的就是第四次的a

此时a就为:

1
2
3
4
5
a = (...args) =>{
return ((...args) => {
return m1(m2(args))
})(m3(args))
}

m3(args)返回了一个新的dispatch

然后依次是m2(args)返回新的dispatch

最后是m1(args)返回新的dispatch

最后m1返回的dispatch作为了新的storedispatch

也就是最后的代码:

1
2
3
4
return {
...store,
dispatch
}

而且可以从这个嵌套看出来,越后面的中间件他的执行距离真正的dispatch会更近。

也就是我们之前使用了两个中间件loggerMiddlewaretimeMiddleware

传入的时候是先loggerMiddlewaretimeMiddleware

输出的时候就是先loggerMiddlewaretimeMiddleware

1
2
3
4
5
loggerMiddleware在dispatch之前的输出
timeMiddleware在dispatch之前的输出
真正的dispatch
timeMiddleware在dispatch之后的输出
loggerMiddleware在dispatch之后的输出

最后就是在createStore中在传入了中间件的情况下直接返回。

1
2
3
4
5
6
7
8
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是整个storedispatch

而构造的中间件函数中的dispatch参数是指包装了剩下中间件的dispatch

一般我们会把这个dispatch写为next,这样就更便于理解了。

1
2
3
4
5
6
7
8
9
10
11
12
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;
};
};
};