Redux 06 Middleware 与 Enhancer

Redux 06 Middleware 与 Enhancer

Redux全家桶概念都搞定啦

来看Redux里边两个比较高级的内容,中间件和强化器(自行翻译)了,文章里还是用英文吧。

先用每个概念一句话来概括:

  1. Middleware增强的是action。
  2. 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的框架要求来写一样,其实熟悉了就很方便。

参数解释

三个参数的内容如下:

  1. store,就是指的redux创建的store,这个参数用于从尚未更新的store中获取状态参与中间件逻辑
  2. next,当处理完action,准备将action传递给下一个中间件的时候,就调用next(action),这是一个由Redux框架传递给中间件的函数,其作用就是调用中间件链条上的下一个中间件进行处理。处理完成的时候,一定要返回next(action)。
  3. 传递给这个中间件的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写法吧。

LICENSED UNDER CC BY-NC-SA 4.0
Comment