课程:React18 系统精讲
章节:Redux中间件
讲师:阿莱克斯刘
课程内容
【中间件】什么是中间件
上节课,我们学习了redux-thunk,初步了解了redux中间件的的作用。本节课,我们来学习一下中间件的原理。
函数式编程概念
在学习中间件之前,我们还是得先补充一点关于函数式编程的基础知识。不过如果同学们已经自学成才了,那么可以跳过这部分的内容。
复合函数
所谓函数式编程,其实就是在做数学题,比如复合函数
。学过高中数学我们都知道函数是可以组合的,对吧。
比如说,我们现在有这么两个函数
f(x) = x^2 + 2x + 3
g(x) = 2x
那么,如果我们把两个函数组合起来可以怎么写呢?
(f * g)(x) = f(g(x)) = f(2x) = 2x^2 + 4x + 6
以上就是数学角度的复合函数,而实际上,在我们的函数式编程中也是完全符合这样的定义的。我们来再来看个例子,演示组合两个函数并返回一个新的函数:
- 首先,我们有一个 hello 函数,输出一个字符串
const hello = function (x) {
return `Hello, ${x}`;
};
- 然后,我们还有一个happy 函数,输出的是一个笑脸
const happy = function (x) {
return `${x} :)`;
};
- 除此之外,我们还有一个组合函数compose,参数接受两个函数,可以把这两个函数按照组合起来。
const compose = function (f, g) {
return function (x) {
return f(g(x));
};
};
- 最后,我们就可以使用这个compose把hello和happy组合起来了
var happyHello = compose(hello, happy);
- 于是,当我们调用happyhello这个函数的时候,他的输出结果就是两个函数组合而成的,比如说,参数传入“阿莱克斯”,那么最后的输出就是:
happyHello("阿莱克斯")
// 输出: Hello, 阿莱克斯 :)
柯里化
复合函数是redux中间件的基础,而柯里化就是实现中间件落地的技术。除此以外,另一个吊炸天的函数式编程概念就是,柯里化。来看下面这个例子,我们可以通过柯里化来组合你的函数,创建一个强大的数据处理管道。
比如说,我们有一个加法计算器,curriedAdd 。通过柯里化,给函数传入第一部分的参数;然后,赋值定义给另一个函数,addTen;最后,在调用addTen函数的时候传入第二部分的参数。
const curriedAdd = (a) => {
return (b) => { return a + b}
};
const addTen = curriedAdd(10);
addTen(10); //20
这时候,两次参数的传递就被积累起来,最后进入curriedAdd函数,进行最终处理。
从原理上来说,如果写成箭头函数的形式,会更容易理解一点,其实原理上就是一个函数的函数。
// 原理
const curriedAdd = (a) => (b) => a + b
为什么需要中间件?
ok,基础知识补充完成,接下来,回到我们的主题,redux中间件。那么,我们为什么会需要中间件?
经过之前的课程,我们知道redux的核心,就是控制和管理所有的数据输入输出,因此有了store、reducer以及action 的 dispatch。
产品经理跑过来提了一个需求
假如现在产品经理跑过来提了一个需求,要求我们的网站可以记录每次dispatch的action信息,我们怎么办呢?最简单直接的办法就是在每一个dispatch的前面加上console.log,然后再dispatch,对吧:
console.log('dispatching', action);
dispatch(action)
产品经理又来了
ok,过了两天这个产品经理又来了,这次他说,我需要每次dispatch还得记录一下当前store的数据state,我们怎么办呢?然后我们又需要在对每一个dispatch做修改
console.log('dispatching', action);
console.log('当前 state', store.getState());
dispatch(action)
但是,如果我们的程序中有很多的dispatch,我们就需要添加很多的重复代码,虽然编辑器可以批量替换,但这无疑是产生了很多同样的模版代码。因此,我们有没有可能重写一下原本的dipatch函数,把日志打印的功能加进去呢?
当然是可以的,我们只需要对原本的dispatch函数进行一下封装就可以了,大概的封装就是这个样子:
let next = store.dispatch
store.dispatch = function dispatchAndLog(action) {
console.log('dispatching', action)
console.log('当前 state', store.getState());
next(action)
}
产品经理没完没了
好了,如果这个时候产品经理又跑过来了,这次他要求,我们不仅要打印当前state,还得打印更改后的state。那么,我们这次又该如何处理呢?很简单,只需要在执行完dispatch next aciton以后,也就是当state发生改变以后,我们直接console log打印state数据就好了。
let next = store.dispatch
store.dispatch = function dispatchAndLog(action) {
console.log('dispatching', action)
console.log('当前 state', store.getState());
next(action)
console.log('更改后的 state', store.getState());
}
那么,如果把这个入口封装一下,给未来的拓展做更广泛的应用,那么,我们可以把整个流程都写在函数里来处理,函数名称叫做,applyMiddleware。
const applyMiddleware = function(middleware){
let next = store.dispatch;
// 这里传入store,因为中间件中有可能会用到getState获取数据,比如打印store
store.dispatch = middleware(store)(next);
}
applyMiddleware(dispatchAndLog)
我们通过把中间件middleware传入函数,使用克丽化来传递参数store和dispatch,然后使用中间件来代理sotre原有的dipatch函数,通过截获所有进入store中的action,来对某些aciton做特殊的处理,比如consolo log打印日志、异步处理等等
Redux中间件机制
简单来讲,Redux middleware 提供了一个分类处理 action 的机会。在 middleware 中,我们可以检阅每一个传递给store的 action,并且挑选出特定类型的 action 进行相应操作。
没有中间件
这样说起来可能会有点抽象,我们直接来看图,这是在没有中间件情况下的 redux 的数据流:
这是很经典的一个redux 的数据流的过程,用户点击button,然后dipatch action,reducer拿到action以后改变state,推送给view重新渲染ui。
增加了中间件
但在增加了 middleware 后,我们就可以在这途中对 action 进行截获,并根据action的类型做出相应的处理。
而 redux middleware 的理论基础的就是复合函数,所以,不同的中间件可以自由组合,而数据也会依次传递。也正是由于这个机制,我们便可以通过串联middleware的形式来满足日常的开发需求,每一个 middleware 都可以处理一个独立的业务需求。
比如我们现在看到的流程图,派发给 redux Store 的 action 对象,会被 Store 上的多个中间件依次捕获。如果把 action 和当前的 state 交给 reducer 处理的过程看做默认存在的中间件,那么其实所有的对 action 的处理都可以有中间件组成的。
值得注意的是这些中间件会按照指定的顺序依次处理传入
的 action,只有排在前面的中间件完成任务后,后面的中间件才有机会继续处理 action,同样的,每个中间件都有自己的“熔断”
处理,当它认为这个 action 不需要后面的中间件进行处理时,后面的中间件就不能再对这个 action 进行处理了。
而不同的中间件之所以可以组合使用
,是因为 Redux 要求所有的中间件必须提供统一的接口
,每个中间件的尉氏县逻辑虽然不一样,但只要遵循统一的接口就能和redux以及其他的中间件对话了
redux的异步处理
在多种中间件中,处理 redux 异步事件的中间件,绝对占有举足轻重的地位。这个部分我们上节课已经解决了,使用的技术是redux-thunk。除了thunk,reudx还有其他的异步处理中间件,比如说redux-promise、redux-saga等等。从原来上来说,这些插件其实都是换汤不换药,基本的原理都一样,他们都代表了各自解决redux异步流管理问题的方案。
-
redux-thunk
-
redux-promise
-
redux-saga
Redux中间件公式
const middleware = (store) => (next) => (action) => { }
下面的公式是Redux中间件的公式,请大家记住这个公式,我们下节课将会利用这个公式,创建一个自定义中间件,来实现课程中产品经理提出的业务要求。