redux进阶 --- 中间件和异步操作

  你为什么需要异步操作? https://*.com/questions/34570758/why-do-we-need-middleware-for-async-flow-in-redux  

  在redux基础篇的介绍中,我们介绍了redux的基本概念, 对于state的改变有了详尽的了解,但是并没有提到异步问题如何解决? 何为异步? Action 发出以后,Reducer 立即算出 State,这叫做同步;Action 发出以后,过一段时间再执行 Reducer,这就是异步。 

  在vue中对异步的实现是在actions下使用,如下所示:

 getDefaultAddress ({commit, state}) {
      return new Promise(function (resolve, reject) {
        axios.get('/bbg/user/get_default_address', {
          params: {
            uid: localStorage.getItem("uid")
          }
        }).then(function (response) {
          if (response.data.code == 152) {
            console.log("获取默认地址成功");
            // 存储默认地址
            commit(UPDATE_DEFAULT_ADDRESS, response.data.data);
          }
          resolve();
        });
      });
    },

  这里是一个异步的操作,即首先执行getDefaultAddress, 然后在执行这个的过程中,我们需要等到返回结果之后再去改变state数据,而vue的方法就是在判断成功的使用commit一个reducer,这样,就可以完成异步的操作了。 

  那么react中是如何实现这种异步操作呢?  这时就需要使用工具: 中间件了。 

  

一、中间件的概念

  为了理解中间件,让我们站在框架作者的角度思考问题:如果要添加功能,你会在哪个环节添加?

1)Reducer:纯函数,只承担计算 State 的功能,不合适承担其他功能,也承担不了,因为理论上,纯函数不能进行读写操作。
(2)View:与 State 一一对应,可以看作 State 的视觉层,也不合适承担其他功能。
(3)Action:存放数据的对象,即消息的载体,只能被别人操作,自己不能进行任何操作。

  想来想去,只有发送 Action 的这个步骤,即store.dispatch()方法,可以添加功能。举例来说,要添加日志功能,把 Action 和 State 打印出来,可以对store.dispatch进行如下改造。

let next = store.dispatch;
store.dispatch = function dispatchAndLog(action) {
  console.log('dispatching', action);
  next(action);
  console.log('next state', store.getState());
}

  

  上面代码中,store.dispatch进行了重定义在发送 Action 前后添加了打印功能。这就是中间件的雏形

  中间件就是一个函数,对store.dispatch方法进行了改造,在发出 Action 和执行 Reducer 这两步之间,添加了其他功能。

个人理解:

将具体业务和底层逻辑解耦的组件。

大致的效果是:
需要利用服务的人(前端写业务的),不需要知道底层逻辑(提供服务的)的具体实现,只要拿着中间件结果来用就好了。

举个例子:
我开了一家炸鸡店(业务端),然而周边有太多屠鸡场(底层),为了成本我肯定想一个个比价,再综合质量挑选一家屠鸡场合作(适配不同底层逻辑)。由于市场变化,合作一段时间后,或许性价比最高的屠鸡场就不是我最开始选的了,我又要重新和另一家屠鸡场合作,进货方式、交易方式等等全都要重来一套(重新适配)。

然而我只想好好做炸鸡,有性价比高的肉送来就行。于是我找到了一个专门整合屠鸡场资源的第三方代理(中间件),跟他谈好价格和质量后(统一接口),从今天开始,我就只需要给代理钱,然后拿肉就行。代理负责保证肉的质量,至于如何根据实际性价比,选择不同的屠鸡场,那就是代理做的事了。

  即中间件实际上就是在某两个步骤之间承担一部分任务,完成某个功能,这就是中间件。 

  

二、 中间件的用法

  本教程不涉及如何编写中间件,因为常用的中间件都有现成的,只要引用别人写好的模块即可。比如,上一节的日志中间件,就有现成的redux-logger模块。这里只介绍怎么使用中间件

  

import { applyMiddleware, createStore } from 'redux';
import createLogger from 'redux-logger';
const logger = createLogger();

const store = createStore(
  reducer,
  applyMiddleware(logger)
);

  

  即先从redux中import使用中间件的插件,然后就可以在创建store的时候使用中间件了。 当然,中间件也是需要提前引入的。 

  上面代码中,redux-logger提供一个生成器createLogger,可以生成日志中间件logger。然后,将它放在applyMiddleware方法之中,传入createStore方法,就完成了store.dispatch()的功能增强。

  补充:我们怎么知道 redux 模块是否含有 applyMiddleware 和 createStore模块呢?  我们可以在 npm install redux --save 之后在node-modules中找到redux,然后在redux中找到入口文件,接着进一步找到内层文件,找到 export , 我们在es/utils/index.js中可以看到导出文件如下: 

  export { createStore, combineReducers, bindActionCreators, applyMiddleware, compose };

  这样,引入applyMiddleware中间件就没有任何问题了。 

  需要注意的是:(1)createStore方法可以接受整个应用的初始状态作为参数,那样的话,applyMiddleware就是第三个参数了。

const store = createStore(
  reducer,
  initial_state,
  applyMiddleware(logger)
);

  (2)中间件的次序有讲究。

const store = createStore(
  reducer,
  applyMiddleware(thunk, promise, logger)
);

上面代码中,applyMiddleware方法的三个参数,就是三个中间件。有的中间件有次序要求,使用前要查一下文档。比如,logger就一定要放在最后,否则输出结果会不正确

 

三、applyMiddlewares()

  上面我们使用了applyMiddlewares()方法,这个方法的作用就是使用中间件,之前,我们也已经找到其源码所在位置,现在粘贴如下所示:

  

export default function applyMiddleware() {
  for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) {
    middlewares[_key] = arguments[_key];
  }

  return function (createStore) {
    return function (reducer, preloadedState, enhancer) {
      var store = createStore(reducer, preloadedState, enhancer);
      var _dispatch = store.dispatch;
      var chain = [];

      var middlewareAPI = {
        getState: store.getState,
        dispatch: function dispatch(action) {
          return _dispatch(action);
        }
      };
      chain = middlewares.map(function (middleware) {
        return middleware(middlewareAPI);
      });
      _dispatch = compose.apply(undefined, chain)(store.dispatch);

      return _extends({}, store, {
        dispatch: _dispatch
      });
    };
  };
}

  即这里可以接受任意多的中间件,然后将所有中间件放在一个middlewares数组中。  接着返回一个函数,使用中间件,在阮一峰老师的博客中也有此段源代码,写法有不同之处,只是这里是es5他的是es6的语法。 

  

四、异步操作的基本思路

  理解了中间件以后,就可以处理异步操作了。

       同步操作只要发出一种 Action 即可,异步操作的差别是它要发出三种 Action

操作发起时的 Action
操作成功时的 Action
操作失败时的 Action

  以向服务器取出数据为例,三种 Action 可以有两种不同的写法。

// 写法一:名称相同,参数不同
{ type: 'FETCH_POSTS' }
{ type: 'FETCH_POSTS', status: 'error', error: 'Oops' }
{ type: 'FETCH_POSTS', status: 'success', response: { ... } }

// 写法二:名称不同
{ type: 'FETCH_POSTS_REQUEST' }
{ type: 'FETCH_POSTS_FAILURE', error: 'Oops' }
{ type: 'FETCH_POSTS_SUCCESS', response: { ... } }

  除了 Action 种类不同,异步操作的 State 也要进行改造,反映不同的操作状态。下面是 State 的一个例子。

let state = {
  // ... 
  isFetching: true,
  didInvalidate: true,
  lastUpdated: 'xxxxxxx'
};

  

五、 redux-thunk 中间件

  这个中间件就可以解决异步actions的多次action触发问题。

六、 redux-promise中间件

  既然 Action Creator 可以返回函数,当然也可以返回其他值。另一种异步操作的解决方案,就是让 Action Creator 返回一个 Promise 对象。

  即此中间件解决的问题和redux-thunk中间件解决的问题相似。