今天,我们要讲解的是自定义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.
  2. // Performs the call and promises when such actions are dispatched.
  3. export default store => next => action => {
  4. const callAPI = action[CALL_API]
  5. if (typeof callAPI === 'undefined') {
  6. return next(action)
  7. }
  8.  
  9. let { endpoint } = callAPI
  10. const { schema, types } = callAPI
  11.  
  12. if (typeof endpoint === 'function') {
  13. endpoint = endpoint(store.getState())
  14. }
  15.  
  16. if (typeof endpoint !== 'string') {
  17. throw new Error('Specify a string endpoint URL.')
  18. }
  19. if (!schema) {
  20. throw new Error('Specify one of the exported Schemas.')
  21. }
  22. if (!Array.isArray(types) || types.length !== 3) {
  23. throw new Error('Expected an array of three action types.')
  24. }
  25. if (!types.every(type => typeof type === 'string')) {
  26. throw new Error('Expected action types to be strings.')
  27. }
  28.  
  29. function actionWith(data) {
  30. const finalAction = Object.assign({}, action, data)
  31. delete finalAction[CALL_API]
  32. return finalAction
  33. }
  34.  
  35. const [ requestType, successType, failureType ] = types
  36. next(actionWith({ type: requestType }))
  37.  
  38. return callApi(endpoint, schema).then(
  39. response => next(actionWith({
  40. response,
  41. type: successType
  42. })),
  43. error => next(actionWith({
  44. type: failureType,
  45. error: error.message || 'Something bad happened'
  46. }))
  47. )
  48. }

例子中,我们用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) {
  2. return (createStore) => (reducer, initialState, enhancer) => {
  3. var store = createStore(reducer, initialState, enhancer)
  4. var dispatch = store.dispatch
  5. var chain = []
  6.  
  7. var middlewareAPI = {
  8. getState: store.getState,
  9. dispatch: (action) => dispatch(action)
  10. }
  11. chain = middlewares.map(middleware => middleware(middlewareAPI))
  12. dispatch = compose(...chain)(store.dispatch)
  13.  
  14. return {
  15. ...store,
  16. dispatch
  17. }
  18. }
  19. }

好短好开心!不过看起来还是挺复杂的,不用担心,不就是个三层嵌套函数嘛,我们先找最外层的执行代码:

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

middlewares使用展开语法,将我们写进去的中间件,生成一个中间件数组。遍历每个中间件,在中间件最外层执行第一次,也就是参数为store那一次。我们发现store是middlewareAPI。

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

是个对象,包含了我们需要的两个方法,这就够了。

好了,我们开始寻找第二层函数的执行代码:

  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'
  2.  
  3. export const USER_REQUEST = 'USER_REQUEST'
  4. export const USER_SUCCESS = 'USER_SUCCESS'
  5. export const USER_FAILURE = 'USER_FAILURE'
  6.  
  7. // Fetches a single user from Github API.
  8. // Relies on the custom API middleware defined in ../middleware/api.js.
  9. function fetchUser(login) {
  10. return {
  11. [CALL_API]: {
  12. types: [ USER_REQUEST, USER_SUCCESS, USER_FAILURE ],
  13. endpoint: `users/${login}`,
  14. schema: Schemas.USER
  15. }
  16. }
  17. }
  18.  
  19. // Fetches a single user from Github API unless it is cached.
  20. // Relies on Redux Thunk middleware.
  21. export function loadUser(login, requiredFields = []) {
  22. return (dispatch, getState) => {
  23. const user = getState().entities.users[login]
  24. if (user && requiredFields.every(key => user.hasOwnProperty(key))) {
  25. return null
  26. }
  27.  
  28. return dispatch(fetchUser(login))
  29. }
  30. }

我们也就是指定这些action来进行“包装的”。除了fetchUser,还有其他的,不列出代码清单了。

我们添加console.log来查看action的真正样子:

  1. console.log('当前执行的action:',action);
  2. const callAPI = action[CALL_API]

在执行initRoutes的action时候,我们得到了一个包含Symbol()的action对象,这就是我们想要的action。

Symbol()

Symbol()是什么?就是CALL_API。

middleware/api.js

  1. // Action key that carries API call info interpreted by this Redux middleware.
  2. export const CALL_API = Symbol('Call API')

为什么要用symbol,为了不冲突,symbol是个唯一的不变的标识,可以 用于对象的key。为了避免冲突而生,这里也可以用字符串来代替,但是不好,因为字符串很容易命名冲突,你每次都要想个奇葩的长名字,所以还是用 symbol吧。再罗嗦一句,我们写object.assign的时候,第一个参数设为{},第二个参数设为源,第三个参数设为拓展属性的写法,就是为了 兼容包含symbol的对象。

消毒

获取到指定的action后,我们要做一系列的异常处理:

middleware/api.js

  1. if (typeof endpoint === 'function') {
  2. endpoint = endpoint(store.getState())
  3. }
  4.  
  5. if (typeof endpoint !== 'string') {
  6. throw new Error('Specify a string endpoint URL.')
  7. }
  8. if (!schema) {
  9. throw new Error('Specify one of the exported Schemas.')
  10. }
  11. if (!Array.isArray(types) || types.length !== 3) {
  12. throw new Error('Expected an array of three action types.')
  13. }
  14. if (!types.every(type => typeof type === 'string')) {
  15. throw new Error('Expected action types to be strings.')
  16. }

比较简单,不罗嗦了。

执行请求action

获取完action,也进行过消毒了,可以开始ajax请求了,首先发一个请求action

  1. function actionWith(data) {
  2. const finalAction = Object.assign({}, action, data)
  3. delete finalAction[CALL_API]
  4. return finalAction
  5. }
  6.  
  7. const [ requestType, successType, failureType ] = types
  8. next(actionWith({ type: requestType }))

执行ajax请求,结束后发出成功或者失败action

  1. return callApi(endpoint, schema).then(
  2. response => next(actionWith({
  3. response,
  4. type: successType
  5. })),
  6. error => next(actionWith({
  7. type: failureType,
  8. error: error.message || 'Something bad happened'
  9. }))
  10. )

图解流程

本来一个action,经过中间件的加工后,变成了一系列的流程。


教程源代码及目录

如果您觉得本博客教程帮到了您,就赏颗星吧!

https://github.com/lewis617/react-redux-tutorial

react+redux教程(七)自定义redux中间件的更多相关文章

  1. Scrapy 教程(七)-架构与中间件

    Scrapy 使用 Twisted 这个异步框架来处理网络通信,架构清晰,并且包含了各种中间件接口,可以灵活的完成各种需求. Scrapy 架构 其实之前的教程都有涉及,这里再做个系统介绍 Engin ...

  2. react+redux教程(八)连接数据库的redux程序

    前面所有的教程都是解读官方的示例代码,是时候我们自己写个连接数据库的redux程序了! 例子 这个例子代码,是我自己写的程序,一个非常简单的todo,但是包含了redux插件的用法,中间件的用法,连接 ...

  3. react+redux教程(五)异步、单一state树结构、componentWillReceiveProps

    今天,我们要讲解的是异步.单一state树结构.componentWillReceiveProps这三个知识点. 例子 这个例子是官方的例子,主要是从Reddit中请求新闻列表来显示,可以切换reac ...

  4. react+redux教程(四)undo、devtools、router

    上节课,我们介绍了一些es6的新语法:react+redux教程(三)reduce().filter().map().some().every()....展开属性 今天我们通过解读redux-undo ...

  5. 【前端】react and redux教程学习实践,浅显易懂的实践学习方法。

    前言 前几天,我在博文[前端]一步一步使用webpack+react+scss脚手架重构项目 中搭建了一个react开发环境.然而在实际的开发过程中,或者是在对源码的理解中,感受到react中用的最多 ...

  6. react+redux教程(二)redux的单一状态树完全替代了react的状态机?

    上篇react+redux教程,我们讲解了官方计数器的代码实现,react+redux教程(一).我们发现我们没有用到react组件本身的state,而是通过props来导入数据和操作的. 我们知道r ...

  7. 【前端,干货】react and redux教程学习实践(二)。

    前言 这篇博文接 [前端]react and redux教程学习实践,浅显易懂的实践学习方法. ,上一篇简略的做了一个redux的初级demo,今天深入的学习了一些新的.有用的,可以在生产项目中使用的 ...

  8. Redux教程3:添加倒计时

    前面的教程里面,我们搭建了一个简单红绿灯示例,通过在console输出当面的倒计时时间:由于界面上不能显示倒计时,用户体验并不良好,本节我们就添加一个简单的倒计时改善一下. 作为本系列的最后一篇文章, ...

  9. Redux教程1:环境搭建,初写Redux

    如果将React比喻成士兵的话,你的程序还需要一位将军,去管理士兵(的状态),而Redux恰好是一位好将军,简单高效: 相比起React的学习曲线,Redux的稍微平坦一些:本系列教程,将以" ...

随机推荐

  1. 01.SQLServer性能优化之----强大的文件组----分盘存储

    汇总篇:http://www.cnblogs.com/dunitian/p/4822808.html#tsql 文章内容皆自己的理解,如有不足之处欢迎指正~谢谢 前天有学弟问逆天:“逆天,有没有一种方 ...

  2. 详解树莓派Model B+控制蜂鸣器演奏乐曲

    步进电机以及无源蜂鸣器这些都需要脉冲信号才能够驱动,这里将用GPIO的PWM接口驱动无源蜂鸣器弹奏乐曲,本文基于树莓派Mode B+,其他版本树莓派实现时需参照相关资料进行修改! 1 预备知识 1.1 ...

  3. EntityFramework Core 1.1是如何创建DbContext实例的呢?

    前言 上一篇我们简单讲述了在EF Core1.1中如何进行迁移,本文我们来讲讲EF Core1.1中那些不为人知的事,细抠细节,从我做起. 显式创建DbContext实例 通过带OnConfiguri ...

  4. 在.NET Core之前,实现.Net跨平台之Mono+CentOS+Jexus初体验

    准备工作 本篇文章采用Mono+CentOS+Jexus的方式实现部署.Net的Web应用程序(实战,上线项目). 不懂Mono的请移步张善友大神的:国内 Mono 相关文章汇总 不懂Jexus为何物 ...

  5. 代码的坏味道(22)——不完美的库类(Incomplete Library Class)

    坏味道--不完美的库类(Incomplete Library Class) 特征 当一个类库已经不能满足实际需要时,你就不得不改变这个库(如果这个库是只读的,那就没辙了). 问题原因 许多编程技术都建 ...

  6. iptables

    一.在服务器上打开 22.80.9011端口: iptables -A INPUT -p tcp --dport 9011 -j ACCEPT iptables -A OUTPUT -p tcp -- ...

  7. 初识的Spring Mvc-----原理

    一.Spring Mvc简介 Spring Mvc(Spring Web Mvc) 属于表现层的框架. 二.Spring结构图 Spring Mvc是Spring框架里面web模块的一部分,是在Spr ...

  8. MySQL 优化之 MRR (Multi-Range Read:二级索引合并回表)

    MySQL5.6中引入了MRR,专门来优化:二级索引的范围扫描并且需要回表的情况.它的原理是,将多个需要回表的二级索引根据主键进行排序,然后一起回表,将原来的回表时进行的随机IO,转变成顺序IO.文档 ...

  9. Android快乐贪吃蛇游戏实战项目开发教程-05虚拟方向键(四)四个三角形按钮

    该系列教程概述与目录:http://www.cnblogs.com/chengyujia/p/5787111.html 一.如何判断点击的是哪个方向键按钮 在上篇教程中我们实现了左边的三角形按钮效果, ...

  10. Bluemix中国版体验(二)

    从上一篇到现在大概有一个多月了.时隔一个月再登录中国版Bluemix,发现界面竟然更新了,现在的风格和国际版已经基本保持一致!这次我们来体验一下Mobile Service.不过mobile serv ...