来看Redux里边两个比较高级的内容,中间件和强化器(自行翻译)了,文章里还是用英文吧。
先用每个概念一句话来概括:
- Middleware增强的是action。
- Enhancer增强的是store。
Middleware
中间件对于搞过Web开发的我,概念其实并不新鲜,Django就有中间件,所有的HTTP请求和响应都会依次通过中间件。Java Web开发中的过滤器,也是一种中间件,对于请求和响应都可以进行处理,依据不同的逻辑,可以决定是否来执行doFilter()
来将请求和响应发送到下一个中间件。
对于中间件的学习,除了官网关于中间件的介绍之外,我还参考了Redux In Action这本书。
Redux的中间件,提供了在派发一个动作和这个动作实际到达reducer之间的第三方代码,实际上相当于在实际执行reducer之前的切面,可以用来对action进行额外的控制,常用来记录日志,崩溃报告,分析,实现异步Action等功能。
中间件和reducer一样,都是注册在store中的。当一个动作被派发的时候,这个动作实际上会经过所有注册在store中的中间件处理,最后实际派发给对应reducer的,是经过了所有中间件处理的action。
要创建中间件,需要先知道中间件需要符合什么样的要求。
标准中间件函数的签名
中间件是一个函数,其函数的标准签名如下:
const loggerMiddleware = store => next => action => {
// 处理action
const processedAction = ......
return next(processedAction);
}
这个形态的函数熟悉柯里化的话,就是这是一个三层嵌套每一层返回内层函数的典型柯里化函数,实际上某种程度也相当于:
const loggerMiddleware = (store, next, action) => {
const processedAction = ......
return next(processedAction);
}
只不过这是Redux的要求,就和filter也要根据Java Web的框架要求来写一样,其实熟悉了就很方便。
参数解释
三个参数的内容如下:
- store,就是指的redux创建的store,这个参数用于从尚未更新的store中获取状态参与中间件逻辑
- next,当处理完action,准备将action传递给下一个中间件的时候,就调用next(action),这是一个由Redux框架传递给中间件的函数,其作用就是调用中间件链条上的下一个中间件进行处理。处理完成的时候,一定要返回next(action)。
- 传递给这个中间件的action,可能是原始的action,也可能是由其他中间件处理过的action,一般会根据这个action的type,结合state来进行处理。
编写日志中间件
这里编写一个简单的日志中间件,每次派发action的时候,按照action.type分组打印action的内容,以及执行完之后的新的state。
根据上边的分析,可以比较容易的编写如下:
const loggerMiddleware = store=>next=>action =>{
console.group(action.type);
console.log(action);
//将action派发给下一个中间件
const result = next(action);
console.log("state: ", store.getState());
console.groupEnd();
return result;
}
然后把这个中间件也加入到store中,这次我们使用第四篇文章里的代码,创建store如下:
export const store = createStore(combinedReducer, {
value: 10,
theme: "daylight",
names: ['saner', 'sitong']
}, applyMiddleware(loggerMiddleware));
这里的第三个参数叫做enhancer,Redux唯一提供的Enhancer就是applyMiddleware函数,这里我们把自己的中间件传递进去了。
然后实际操作的时候,可以看到控制台的打印:
VALUE_TYPE
{type: 'VALUE_TYPE', number: 1}
VALUETYPE REDUCER 执行了
state: {value: 11, theme: 'daylight', names: Array(2)}
VALUE_TYPE
{type: 'VALUE_TYPE', number: 1}
VALUETYPE REDUCER 执行了
state: {value: 12, theme: 'daylight', names: Array(2)}
每次执行action的时候,打印出了action和执行后的store内容。
编写偷天换日中间件
在原来的代码中,我们每次发送VALUETYPE的时候会增加1,我们可以通过传入中间件,来让每次增加的值变成6,编写中间件如下:
import actionTypes from "../action/actionType";
import {valueTypeCreator} from "../action/actionBuilder";
export const sixMiddleware = store => next => action => {
let newAction = action;
if (action.type === actionTypes.VALUE_TYPE) {
newAction = valueTypeCreator(6);
}
console.group(newAction.type);
console.log(newAction);
const result = next(newAction);
console.log("state: ", store.getState());
console.groupEnd();
return result;
};
之后在创建store的时候使用这个中间件:
export const store = createStore(combinedReducer, {
value: 10,
theme: "daylight",
names: ['saner', 'sitong']
}, applyMiddleware(sixMiddleware));
就会发现,虽然发送的是增加1的action,但中间件里偷偷改成了增加6的action,最后结果就是增加了6。
写到这里我发现,这玩意本质和以前用过的中间件没有什么本质的区别,好比Django那中间件,给你在Http请求和响应上加上什么东西,和Redux的中间件干的事情是一样的。
Enhancer
Middleware前边已经知道了,是增强action的功能。Enhancer用官网的话来说,类似于一个特别版的createStore函数,在原始的store对象外边进行包裹。Enhancer可以改变store的行为,即改变store的dispatch,getState和subscribe这些API的功能(替换了原始版本)。
还记得applyMiddleware吗,这个就是一个enhancer,applyMiddleware(thunk)的作用,实际上就是改变了dispatch这个方法的功能,让其可以接受函数作为参数。
Enhancer的基础签名
一个Enhancer也是一个函数,标准签名如下:
const enhancerCreator = createStore => (...args) => {
//创建store,这个是标准写法,不能改
const store = createStore(...args);
//自己编写函数或者其他东西,创建一个新的newStore对象包装原来的store对象
return newStore;
}
看了签名就知道,实际上就是使用内置的createStore先创建一个store,然后对这个store做手脚,让store的功能产生变化,甚至打上猴子补丁。
搞怪的getState()
我们编写一个Enhancer如下:
export const printSomething = createStore => (...args) => {
const store = createStore(...args);
const getState = () => ({
...store.getState(),
trick: "Enhancer搞的鬼",
});
return {...store, getState}
}
注意,不能在enhancer里边直接就按照在正常程序中的方法创建store,否则中间件会直接绑定到所创建的store中,达不到想要的结果。
和其他的enhancer一起使用
注意到我们之前使用了applyMiddleware(sixMiddleware)这个enhancer。
我们现在又编写了一个,需要使用Redux提供的compose函数来组合多个enhancer,组合的方法也很简单,只要将多个enhancer传递给compose函数即可,现在创建store的代码如下:
export const store = createStore(combinedReducer, {
value: 10,
theme: "daylight",
names: ['saner', 'sitong']
}, compose(applyMiddleware(sixMiddleware), printSomething));
此时再去点击自增1的按钮,可以看到打印如下:
VALUE_TYPE
{type: 'VALUE_TYPE', number: 6}
VALUETYPE REDUCER 执行了
state: {value: 16, theme: 'daylight', names: Array(2), trick: 'Enhancer搞的鬼'}
getState()函数返回的state中,多了我们加上的那一条搞怪信息。
如果去看源码的话,实际上applyMiddleware接受了...middlewares参数之后,返回的就是一个符合enhancer签名的函数,所以说applyMiddleware就是一个enhancer。
后记
这两个东西,middleware可以对某些特定类型的action进行统一操作,enhancer则是对store的功能可以统一操作。目前会了编写,不过感觉暂时还用不到这么高级的功能,而且调试的功能Redux也做的很全面了,后面遇到具体问题的时候可以考虑用一下这两个东西。目前传统的Redux的部分就搞定了。后边看一下是继续看Router,还是要了解一下现代的Redux写法吧。