今天,我们要讲解的是自定义Redux中间件这个知识点。本节内容非常抽象,特别是中间件的定义原理,那多层的函数嵌套和串联,需要极强逻辑思维能力才能完全消化吸收。不过我会多罗嗦几句,所以不用担心。
例子
例子是官方的例子real-world,做的是一个获取github用户、仓库的程序。
本例子源代码:
https://github.com/lewis617/react-redux-tutorialt/tree/master/redux-examples/real-world
Redux中间件就是个嵌套函数
Redux中间一共嵌套了三层函数,分别传递了store
、next
、action
这三个参数。是不是想到了函数式编程中的柯里化?
为什么要嵌套函数?为何不在一层函数中传递三个参数,而要在一层函数中传递一个参数,一共传递三层?因为中间件是要多个首尾相连的,对next
进行一层层的”加工”,所以next
必须独立一层。那么store
和action
呢?store
的话,我们要在中间件顶层放上store
,因为我们要用store
的dispatch
和getState
两个方法。action
的话,是因为我们封装了这么多层,其实就是为了作出更高级的dispatch
方法,但是在高级也是dispatch
,是dispatch
,就得接受action
这个参数。
看我们例子中的代码:
middleware/api.js
1 | // A Redux middleware that interprets actions with CALL_API info specified. |
例子中,我们用store => next => action =>
实现了三层函数嵌套。箭头语法很好的代替了丑陋的function嵌套方法。最后指向的那个{}里面,我们就可以写关于dispatch
的装饰了,不过记得返回next
,给下一个中间件使用。
中间件的执行
嵌套函数也是函数,是函数就要运行。我们知道,js里面,我们用()
来执行一个函数。那么三层嵌套函数我们要怎么执行呢?写三个()
呗!aaa()()()
就可以了。aaa()
返回了一个函数,aaa()()
又返回一个函数,aaa()()()
终于执行完成。
我们来看下,我们编写的中间件是怎么运行的。首先中间件的使用是在configStore
里面的applyMiddleware
里面写的。
1 | applyMiddleware(thunk, api) |
我们看下Redux的源代码:
node_modules/redux/src/applyMiddleware.js
1 | export default function applyMiddleware(...middlewares) { |
好短好开心!不过看起来还是挺复杂的,不用担心,不就是个三层嵌套函数嘛,我们先找最外层的执行代码:
1 | chain = middlewares.map(middleware => middleware(middlewareAPI)) |
middlewares
使用展开语法,将我们写进去的中间件,生成一个中间件数组。遍历每个中间件,在中间件最外层执行第一次,也就是参数为store
那一次。我们发现store
是middlewareAPI
。
1 | var middlewareAPI = { |
是个对象,包含了我们需要的两个方法,这就够了。
好了,我们开始寻找第二层函数的执行代码:
1 | dispatch = compose(...chain)(store.dispatch) |
这是什么鬼?compose
是种函数嵌套的写法,源代码清单就不展示了,我们知道它可以帮我们将嵌套的函数,写成逗号隔开的样子就可以了。有点类似Promise解决回调地狱的做法。都是将嵌套写成平行的样子。
chain
就是我们的第二层函数,…就是展开语法,用逗号隔开放进compose
参数里。后面那个(store.dispatch)
就是入口参数。是个未经任何加工的dispatch
,这个可怜的家伙进去后,将被我们的中间件层层加工,经历风雨,变成更加牛逼的dispatch
。
第三层函数的执行代码在哪?不在这里面,因为到了第三层函数,就是新的dispatch
了,我们要在组件里面使用它,所以第三层函数的执行在组件中。参数是什么?当然是可爱的action
啊!
中间件的连接
我们知道,中间件是可以首尾相连使用的,那么我们如何实现首尾相连?答案就在next()
,写过express中间件的同学对它一定不陌生。那么Redux中间件的next()
是个什么鬼?也是个dispatch
。
我们写中间件时候,一定要写next()
,当前中间件对dispatch
加工后返回给后面的中间件继续加工。这也是为什么中间件的顺序有讲究的原因。
解读例子中的中间件的业务流程
中间件的原理讲完了,我们来走一遍例子中的中间件业务流程吧!
获取指定action
我们在定义action的时候,在fetchUser
等函数中返回了这些我们需要的东西:
actions/index.js
1 | import { CALL_API, Schemas } from '../middleware/api' |
我们也就是指定这些action来进行”包装的”。除了fetchUser
,还有其他的,不列出代码清单了。
我们添加console.log
来查看action的真正样子:
1 | console.log('当前执行的action:',action); |
在执行initRoutes的action时候,我们得到了一个包含Symbol的action对象,这就是我们想要的action。
Symbol
Symbol是什么?上述代码中的CALL_API
就是Symbol。
middleware/api.js
1 | // Action key that carries API call info interpreted by this Redux middleware. |
为什么要用Symbol,为了不冲突,Symbol是个唯一的不变的标识,可以用于对象的key。为了避免冲突而生,这里也可以用字符串来代替,但是不好,
因为字符串很容易命名冲突,你每次都要想个奇葩的长名字,所以还是用Symbol吧。再罗嗦一句,我们写Object.assign的时候,
第一个参数设为{},第二个参数设为源,第三个参数设为拓展属性的写法,就是为了兼容包含Symbol的对象。
消毒
获取到指定的action后,我们要做一系列的异常处理:
middleware/api.js
1 | if (typeof endpoint === 'function') { |
比较简单,不罗嗦了。
执行请求action
获取完action,也进行过消毒了,可以开始ajax请求了,首先发一个请求action
1 |
|
图解流程
本来一个action,经过中间件的加工后,变成了一系列的流程。
教程示例代码及目录
http://www.liuyiqi.cn/2016/12/10/react-redux-tutorial-catalog/