react middleware

  • 前言
    react已经出来很久了,其生态圈之庞大,一锅炖不下!各种react-xx,已让我们不堪重负,github上随便一个demo,引入的模块至少都是五指之数+。看着头疼,嚼之无味……。
    在此建议新学者,可以从基础的核心模块学起,前期不要考虑那些数量繁多的马仔小弟,边学边写,个人感觉前期核心要学的流程大致如下:
    React ——> React + redux + React-redux ——> React + redux + React-redux + React-router ——> React + redux + React-redux + React-router ;
    其它的,看情况学习和了解,我也很菜,以上感悟都是针对初学者,希望可以减少他们在学习过程中接触过多的东西,而影响学习信心和乐趣。
  • 文档
    React小书(作者从无到有,讲述了React的起源,通俗易懂)Note: 第三阶段的文档现在开始收费查看了,不过对于搞前端的人来说不用钱也可以来个亲密接触的(大家自己想办法)
    Redux莞式教程(抛开需求讲实用性都是耍流氓,作者扮演一位PM给我们上了生动的一课,深入浅出,简明扼要)
    React-Router文档(一部中规中矩的翻译之作)

以上是整理的一些说明和文档资料,没有看过的可以去了解一下。下面将开始本文的主题:redux的中间件applyMiddleware。

都说名字越长,越让学者害怕,applyMiddleware的名字看起来就挺吓人,那么为什么会出现中间件,它是做什么的?它为什么叫中间件?为什么说可以用来解决异步dispatch?经过一段时间的了解,让我渐渐明白了它的工作原理,现在让我们带问题,怀着简单,轻松的心态走进applyMiddleware大讲堂:

 

  1. 为什么会出现中间件?
    我们知道redux的核心,就是控制和管理所有的数据输入输出,因此有了dispatch,由于dispatch是一个很纯的纯函数,就是单纯的派发action来更改数据,其功能简单且固定。
    假如现在产品经理A某有个需求,要求记录每次的dispatch记录,我们怎么办呢?最简单直接的办法就是在每一个dispatch的前面加上:
    console.log('dispatching', action);
    dispatch(action)

     假如又来一个产品B说,我需要记录每次数据出错的原因,我们怎么办呢?然后我们又需要在对每一个dispatch做修改

    try{
      dispatch(action)    
    }catch(err){
      console.error('错误报告: ', err)  
    }  

    如果我们的程序中有很多的dispatch,我们就需要添加很多的重复代码,虽然编辑器提供批量替换,但这无疑是产生了很多样板代码。
    因为所有的需求都是和dispatch息息相关,所以只要我们把日志放进dispatch函数里,不就好了吗,我们只需要更改dispatch函数,把dispatch进行一层封装。
    大概的封装就是下面这样:

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

    Redux把这个封装的入口写成了一个函数,就叫applyMiddleware。
    由此我们明白了applyMiddleware的功能:改造dispatch函数,产生真假dispatch,而中间件就是运行在假真(dispatchAndLog和next)之间的代码。
    这里我们要对applyMiddleware进行一个准确的定义,它只是一个用来加工dispatch的工厂,而要加工什么样的dispatch出来,则需要我们传入对应的中间件函数(比如上例中的dispatchAndLog),下面我们构造一个精简版的applyMiddleware:

    const applyMiddleware = function(middleware){
      let next = store.dispatch;
      store.dispatch = middleware(store)(next);  // 这里传入store,是因为中间件中有可能会用到getState获取数据,比如打印当前用户等需求
    }
    
    applyMiddleware(dispatchAndLog) 

     

  2. 中间件的串联融合。
    中间件的功能各不相同,它们都要融入到dispatch中,在派发action的时候,按照顺序一个个的执行,这是一个费脑经的事情。
    假如现在我们有两个中间件 logger和collectError两个中间件函数,那么大概的执行顺序就是 dispatch——>logger改造——>collectError改造。这里我们能看到后面的中间件需要接收到前面改造后的dispatch,
    在前面,我们是直接修改store.dispatch,现在我们换一种写法,让每一个中间件函数,接收一个dispatch,然后返回一个改造后的dispatch,来作为下一个中间件函数的next,以此类推,用ES6的写法大概代码如下:
    复制代码
    const logger = store => next => action => {
      console.log('dispatching', action)
      return next(action)
    }
    
    const collectError = store => next => action => {
      try {
        return next(action)
      } catch (err) {
        console.error('Error!', err)
      }
    }
    复制代码

     然后,我们改造一下applyMiddleware,来接收一个middlewares数组:

    复制代码
    function applyMiddleware(middlewares) {
      middlewares = middlewares.slice()
      middlewares.reverse()
    
      let dispatch = store.dispatch
      middlewares.forEach(middleware =>
        dispatch = middleware(store)(dispatch)
      )
      return Object.assign({}, store, { dispatch })
    }
    复制代码

     

    上面的middleware(store)(dispatch) 就相当于是 const logger = store => next => {},这就是构造后的dispatch,继续向下传递。这里middlewares.reverse(),进行数组反转的原因,是最后构造的dispatch,实际上是最先执行的。因为在applyMiddleware串联的时候,每个中间件只是返回一个新的dispatch函数给下一个中间件,实际上这个dispatch并不会执行。只有当我们在程序中通过store.dispatch(action),真正派发的时候,才会执行。而此时的dispatch是最后一个中间件返回的包装函数。然后依次向前递推执行。
    我们拿logger和collectError来说明:

    构造过程:

    复制代码
    let next = store.dispatch;
    let dispatch1 = logger(store)(next); 
    // 这时候的console.log('dispatching', action) 是没有执行的
    
    let dispatch2 = collectError(store)(dispatch1);
    // 这时候的console.log('Error!', err) 也是没有执行的
    
    store.dispatch = dispatch2;
    复制代码

    执行过程:

    复制代码
    store.dispatch(action); //假如我们程序中派发了某个action
    
    //相当于是下面这样
    dispatch2(action); //此时执行了 console.log('Error', err)
    
    //由于collectError中间件中的next是接收的logger返回函数即dispatch1,所以在开始执行
    dispatch1(action); //此时执行了 console.log('dispatching', action)
    
    // 这个例子不太合理,因为错误报告是先 try 的 next(action),但是正常的流程是如此。
    复制代码


middleware有什么用?为什么要引入它呢?

 

在redux里,action仅仅是携带了数据的普通js对象( plain JavaScript objects)。action creator返回的值是这个action类型的对象。然后通过store.dispatch()进行分发……

action ---> dispatcher ---> reducers

 

如果遇到异步情况,比如点击一个按钮,希望2秒之后更新视图,显示消息“Hi”。我们可能这么写ActionCreator:

复制代码
var asyncSayActionCreator = function (message) {
    setTimeout(function () {
        return {
            type: 'SAY',
            message
        }
    }, 2000)
}
复制代码

  这会报错,因为这个asyncSayActionCreator返回的不是一个action,而是一个function。这个返回值无法被reducer识别。

也就是说,正常来说,action返回的是一个对象,而不是一个函数。如果返回函数,会出现错误。

  而异步操作呢,需要action的返回值是一个函数。那么咋办呢,所以需要引入中间件middleware,它在中间起到了桥梁的作用,让action的返回值可以是一个函数,从而传到

reducer那里。也就是说,中间件是用在action发起之后,reducer接收到之前的这个时间段。

也可以这么说,Middleware 主要是负责改变Store中的dispatch方法,从而能处理不同类型的 action 输入,得到最终的 Javascript Plain Object 形式的 action 对象。

  因此,上面那个ActionCreator就可以改写为这样:因为action的返回值是一个函数。

复制代码
var asyncSayActionCreator = function (message) {
    return function (dispatch) {
        setTimeout(function () {
            dispatch({
                type: 'SAY',
                message
            })
        }, 2000)
    }
}
复制代码

 

中间件是如何工作的

Middleware的中间件有很多,不过我的这个案例只引用了其中的一个,那就是redux-thunk

redux-thunk源码如下:

export default function thunkMiddleware({ dispatch, getState }) {
  return next => action =>
    typeof action === 'function' ?
      action(dispatch, getState) :
      next(action);
}

   意思是如果action是一个函数,执行这个action函数,如果不是函数,执行next函数。


常用的middleware

redux-logger

redux-thunk





©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页