redux的生态中,提供了中间件(即middleware)的方式来增强redux。middleware是指可以被嵌入在框架接收请求到产生响应过程之中的代码。middleware的方式能够很好的对代码进行解耦,如此middleware就能够独立成一个个的功能模块,方便维护和升级。那在redux中是如何提供对middleware的支持呢?redux提供了applyMiddleware()方法来支持middleware。applyMiddleware()方法的实现思路,就是将这些middleware形成一个链表,当dispatch()一个action的时候,就逐个调用调用链表里的middlware。它看起来就像下面这样。

middleware

实现的代码像下面这样:

function applyMiddleware(store, middlewares) {
  middlewares = middlewares.slice()
  middlewares.reverse()

  let dispatch = store.dispatch
  middlewares.forEach(middleware =>
    dispatch = middleware(store)(dispatch)
  )

  return Object.assign({}, store, { dispatch })
}

这段代码,首先将middlewares逆序排列,然后遍历这个逆序的middlewares,获取每个middleware的返回函数赋给dispatch,遍历完成后dispatch变量就是第一个middleware的返回函数,最后把store中的dispatch()方法替换成了第一个middleware的返回函数。所以如果我们添加了中间件,在执行store.dispatch()方法时,这个方法已经不是redux本身的dispatch()方法,而是第一个middleware的返回函数,redux本身的dispatch()方法会在所有middlewares的返回函数都执行后被调用。

同时,我们也注意到在遍历middlewares,我们对middleware进行了两次调用,分别传入了store和dispatch变量,即middleware(store)(dispatch),store变量是一个固定的值,但是dispatch在每一次遍历后都重新指向了前一个middleware的返回值,在官方文档中这个dispatch变量命名为next,其实是更合理的。所以我们在编写middleware的时候,需要遵循如下的写法:

const middleware = (store) => (next) => (action) => {
    // ... code
    const result = next(action)
    // ... code
    return result
}

store的传入只是方便在middleware中获取应用的状态,而next的传入是为了形成一个链表,指向下一个middleware,所以在自己编写middleware的时候,需要在middleware内部手动调用next(action)以便能调用下一个middleware。

1、logger中间件

logger中间件的目的,是在dispatch执行前做一次记录,dispatch完成后,打印出dispatch之后的state。按照middleware的方式,我们来完成这个组件。

// middlewares/logger.js
const logger = (store) => (next) => (action) => {
    console.log('dispatching', action)
    next(action)
    console.log('next state', store.getState())
}

export default logger

logger中间件,在执行next之前打印出当前的action,然后执行next(即往后继续执行其他的middleware或者执行redux的dispatch()),当dispatch完成后,打印出state信息。

2、thunk middleware

我们都知道,如果想要使用redux dispatch一个function类型的action,需要添加一个redux-thunk的middleware。那它是怎样实现的呢?我们来尝试实现它。

// middlewares/thunk.js
const thunk = (store) => (next) => (action) => {
    if (typeof action === 'function') {
        action(store.dispatch, store.getState)
    } else {
        next(action)
    }
}

export default thunk

上面这段代码,仍然采用了redux middleware的实现模式来包装(即(store) => (next) => (action))。在实现的内部,我们判断传递进来的action,如果不是一个function,那么就调用下一个middleware;如果是function,我们就执行这个action,并且把redux的dispatch()和getState()方法参数传入到这个action方法,到此调用链结束。

在acion生成方法的内部,如果使用了dispatch()方法重新发起action,则会沿着middlewares的调用链表重新执行。一般dispatch一个function action,会像下面这样:

// 使用示意-action生成方法
export function dispatchInitTodos() {
  return (dispatch) => {
    getTodos().then((data) => {
      dispatch({
        type: 'INIT_TODOS',
        data,
      })
    })
  }
}
store.dispatch(dispatchInitTodos())

3、promise middleware

dispatch一个Promise,和dispatch一个function会不会是一样的方式呢?我们先来写一个使用promise作为action的代码。

function dispatchPromise(){
  return  new Promise((resolve, reject) => {
    // ...
  })
}

如上述代码,我们dispatch一个Promise,这个Promise对象在创建出来的时候,就已经开始执行内部的逻辑了。这和function还是不一样的,function定义出来之后,可以延后执行,所以我们可以在middleware的实现中调用function。

所以在实现promise middleware的时候,思路是将Promise的resolved(或者rejected)的值作为一个action,重新使用store.dispatch()方法来发起这个action。

// middlewares/promise.js
const promise = (store) => (next) => (action) => {
    // 抽取一个dispatch的方法
    // 支持派发多个action
    const dispatchInnerAction = (data) => {
        if (Array.isArray(data)) {
            data.forEach(item => {
                store.dispatch(item)
            })
        } else {
            store.dispatch(data)
        }
    }
    if (typeof action.then === 'function') {
        action.then((data) => {
            dispatchInnerAction(data)
        }, (err) => {
            dispatchInnerAction(err)
        })
    } else {
        next(action)
    }
}

export default promise

上述这段代码,当action.then为一个function的时候,我们就断定这个action为一个Promise对象。我们调用action.then()方法来获取这个Promises对象的resolved值(或者rejected值)。当然,这里resolved值(或者rejected值)要求必须为一个action。为了支持多个action,对resolved值(或者rejected值)进行了判断,如果是一个数组,逐个遍历并dispatch,否则直接dispatch。

Promise action的生成函数大体如下:

// actions.js
import { getTodos } from './services.js'

// 初始化todos
export function initTodos(list) {
  return {
    type: 'INIT_TODOS',
    list,
  }
}

export function dispatchInitTodosPromise(){
  return  getTodos().then((data) => {
    return initTodos(data.list)
  })
}

上述代码,getTodos()方法本身返回了一个Promise,我们调用getTodos().then()后传递给middleware的依然是一个Promise,并且使用 return initTodos(…)的形式resolved了一个action。在promise middleware中,这个resolved了的action会使用store.dispatch()方法发起。

4、结语

本文我们学习了redux middleware的实现机制,同时举了几个例子来编写自定义的middleware。如果不明白,可以到redux的官方文档中,查看 middlewares 这篇教程,里面很详尽地道出了middleware的演进思路。不过这篇文章中举的两个例子,logger和crashReporter,虽然简单易懂,但是代表性不是特别强,最好自己动手实现一个简单的redux-thunk和redux-promise,对于redux middleware的理解将会更加深入。

本章的实现代码可以查看redux-06