如何编写一个 Redux 中间件

要想实现一个 redux 中间件咱们必须了解 redux 的基本实现原理。本文将从 redux 源码入手,重点讲解 applyMiddleware 如何将中间件串联执行。只有理解了底层原理咱们才能够游刃有余的写出一个 redux 中间件。javascript

目录

createStore 源码解读

redux 经过 createStore 来建立一个 store 对象html

要理解 applyMiddleware 的实现原理,咱们要从 createStore 入手java

export default function createStore(reducer, preloadedState, enhancer) {
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }

    return enhancer(createStore)(reducer, preloadedState)
  }

  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
  }
  // 篇幅有限,后面被我省略了,有兴趣请去看 redux 源码
  // ......
复制代码

能够看见 createStore 的三个参数依次为: reducer, preloadedState, enhancer。参见源码,若是传入了 enhance 参数且为函数,则将 createStore 传入 enhancereact

return enhancer(createStore)(reducer, preloadedState)ios

也就是说,如今咱们将用 enhance 来建立一个 store 对象。json

applyMiddlewave 源码解读

通常状况下 createStore 的第三个参数 enhance 就是 applyMiddlewaveredux

applyMiddlewave 的代码只有二十多行倒是本文的重点axios

export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState, enhancer) => {
    var store = createStore(reducer, preloadedState, enhancer)
    var dispatch = store.dispatch
    var chain = []

    var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}
复制代码

参见 createStore 的源码能够得知:applyMiddlewave 依然使用 createStore 建立了store 对象而且返回,只是改写了这个对象的 dispatch 方法。数组

下面咱们重点来看这个被改写掉的 dispatch 方法,同时理解它和原生 dispatch 方法的区别也是本文的重点。为了更直观的了解这个过程咱们先来看一个 简单的中间件实现 logger middlewave闭包

export default store => next => action => {
    const start = Date.now();
    next(action);
    const ms = Date.now() - start;
    console.log(`dispatch: ${action.type} - ${ms}ms`);
}
复制代码

下面分二步详细探讨中间件的运行原理

  1. 将原生的 getState 和 dispacth 做为第一个参数传入中间件数组,得到执行完的 chain 数组;

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

  2. 组合串联 middlewave

    dispatch = compose(...chain)(store.dispatch)

    compose 将全部的中间件串联起来组成新的 dispatch

    compose 源码

    function compose(...funcs) {
        return arg => funcs.reduceRight((composed, f) => f(composed), arg);
    }
    复制代码

    参考咱们的 logger middlewave 这里的 composed 便是咱们的 next 参数。

    reduceRight 和 ruduce 同样,不过 reduceRight 是从数组的右端开始执行,arg 将做为 reduceRight 的初始值(这里就是 store.dispatch)。假设咱们的 chain 数组为 [f1,f2,f3]执行完毕后 dispatch 为 dispatch = f1(f2(f3(store.dispatch)))),调用这个新的 dispatch 每一个中间件就能依次执行了,这里的中间件执行过程也是相似于 Koa 的中间件是很是经典的洋葱模型。只有最后一个中间件会触发 redux 原生的 dispatch,将这个 action 分发出去。(没错,我就是灵魂画师)

洋葱模型

redux-thunk 的实现原理

通常而言 dispatch 只能分发一个 action 对象,可是使用了 redux-thunk 中间件咱们却能够分发一个异步函数。

const thunk = store => next => action => {
    typeof action === 'function' ?
        action(store.dispatch,store.getState) :
        next(action)
}
复制代码

一个异步的 action 的示例

function getMessage = (dispatch, getState) => {
    axios.get('xxx/xxx')
    .then((res) => {
        dispatch({
            type: 'GET_MESSAGE_SUCCESS',
            message: res.json(),
        });
    })
    .catch((err) => {
        dispatch({
            type: 'GET_MESSAGE_ERROR',
            message: 'error'
        });
    });
}
复制代码

这里的 dispatch 任然是改造后的 dispatch 由于传入中间件的第一个参数 store 即 middlewareApi 中的 dispatch 是一个闭包保存着对最外层函数 dispatch 的引用,因此当 diapatch 被改写后后面调用的 dispatch 都是这个新的 dispatch(即中间件的串联),因此即便在异步 action 中分发一个 action 依然会将所有中间件再执行一遍。

如何编写一个中间件

因此理解了以上,编写一个中间件将超级简单,只须要按照中间件编写规范

function myMiddleware = store => next => action = {
    // 在这里你能够拿到 store.getState 和 store.dispatch
    // 注意若是你调用 store.dispatch 中间件又重新从最外层开始 若是不加限制条件将陷入死循环
    // do something
    next(action)   // 进入下一个中间件,最后一个中间件的 next 参数为 redux 原生 dispatch
    // 返回继续执行这个中间件剩余部分
}
复制代码

总结

深刻理解 redux 中间件的实现原理,可让咱们在平常工做中,对 redux 数据流向更加清晰和对本身的程序更加有把握。本人水平有限,若有错误还请指出。

参考资料

redux 官方文档

《深刻 react 技术栈》

阮一峰 redux 入门教程

原创文章,转载请注明原地址

若是你喜欢的话,可不能够给我点个当心心(*^__^*) 嘻嘻……

相关文章
相关标签/搜索