Redux中间件源码剖析

redux中间件

redux 是一个轻量级的数据流管理工具,主要解决了 component -> action -> reducer -> state 的单向数据流转问题。同时, redux 也提供了类似于 koa 和 express 的中间件(middleware)的概念,让我们可以介入数据从 actionreducer 之间的传递过程,从而改变数据流,实现如异步、数据过滤、日志上报等功能。

redux 的中间件是通过第三方插件的方式实现,本身源码也不是很多,我们就从源码来解读 redux 的中间件机制。

首先来看我们是如何加载一个中间件的,以 redux-thunk 为例:

import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import reducers from './reducers.js';

let store = createStore(
    reducers,
    preloadedState,
    applyMiddleware(thunk)
);
// ...

加载中间件有两个核心的方法: createStoreapplyMiddleware ,接下来我们就从源码剖析,看 redux 中间件的运行原理到底是怎么样的。

applyMiddleware

首先看一下 applyMiddleware 的源码:

import compose from './compose'

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
    }
  }
}

这就是 applyMiddleware 方法的全部内容,我们细剖来看。首先, applyMiddleware 方法接收一个或多个中间件作为参数(会被函数作为ES6的 rest 参数读取,变成一个数组),然后返回了一个匿名函数:

return (createStore) => (reducer, preloadedState, enhancer) => {
    ...
}

这种写法同样是 ES6 的写法,翻译成 ES5 其实就是:

return function (createStore) {
    return function (reducer, preloadedState, enhancer) {
        ...
    }
};

也就是说,负责加载中间件的 applyMiddleware 方法其实只是返回了一个带有一个入参的匿名函数。此时,createStore 方法执行的时候即为:

let store = createStore(
    reducers,
    defaultReducer,
    function (createStore) {...} // applyMiddleware(thunk)
);

接下来就来看看 createStore 做了什么。

createStore

同样先来看一看 createStore 的源码:

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)
  }
  
  var currentReducer = reducer
  var currentState = preloadedState
  var currentListeners = []
  var nextListeners = currentListeners
  var isDispatching = false

  function ensureCanMutateNextListeners() {...}
  function getState() {return currentState;}
  function subscribe(listener) {...}
  function dispatch(action) {...}
  function replaceReducer(nextReducer) {...}
  function observable() {...}
  
  dispatch({ type: ActionTypes.INIT })

  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }
}

createStore 函数接收三个参数:

  • reducer :即我们通过 combineReducers 导出的 reducer 集合;
  • preloadedState :可选参数,初始化的 state
  • enhancer :用来增强 store ,也就是通过 applyMiddleware 返回的匿名函数。

逐块分析代码:

if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
}

这块代码来处理 preloadedState 可选参数,处理是两个参数还是三个参数的情况,比较简单。简单概括就是,如果只传了两个参数,并且第二个参数为函数,第二个参数会被当作 enhancer

继续往下看:

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

    return enhancer(createStore)(reducer, preloadedState)
}

这块代码其实是 redux 中间件的核心入口,也是有无中间件处理流程的分叉口。如果我们注册了中间件,就会执行 enhancer ,而如果没有注册的话,就直接往下执行然后返回 dispatch, getState 等等这些东西了。我们来看注册中间件的情况下, enhancer 方法执行的时候发生了什么。

enhancer 就是上面讲过的 applyMiddleware 函数返回的匿名函数。 enhancer 方法接收一个参数: createStore ,你没看错,就是拥有 reducer, preloadedState, enhancer 这三个参数的 createStore

// applyMiddleware(thunk) 返回的匿名函数
// 接收了 enhancer 传来的 createStore
return function (createStore) { // 第一层匿名函数
    // 接收了 enhancer(createStore) 传来的 reducer, preloadedState
    return function (reducer, preloadedState, enhancer) { // 第二层匿名函数
        ...
    }
};

实际上,enhancer(createStore)(reducer, preloadedState) 执行的时候,参数 createStore 给了第一层匿名函数,因为我们的目的是要对 createStore 进行修饰。而 reducerpreloadedState 两个参数给了第二层匿名函数。

第二层匿名函数同样拥有 reducer, preloadedState, enhancer 三个参数,也即:

// 接收了 enhancer(createStore) 传来的 reducer, preloadedState
return function (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
    }
}

那我们就来看一看这个匿名函数又做了什么事情。

var store = createStore(reducer, preloadedState, enhancer)

首先,第二层匿名函数又调了 createStore 方法(又回去了…orz)。刚才也说到,在我们应用入口createStore 方法的时候,第三个参数 enhancer 其实传的是我们注册的中间件。而这时,createStore 接收到的参数只有 reducerpreloadedState ,也就是说会按照正常的没有注册中间件的情况,直接往下执行然后返回 dispatch, getState 等等这些东西。所以这时候 store 拿到的是:

return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
}

接着往下看。

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)

别忘了,我们目前执行的第一层匿名函数和第二层匿名函数,都是在 applyMiddleware 方法的作用域内(都是 applyMiddleware 返回的匿名函数),所以可以直接访问 middlewares 参数。上面 chain 的值就是对中间件进行map,也就是调用中间件的方法。我们以 redux-thunk 为例,看一下 redux-thunk 的源码:

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

是的, redux-thunk 源码就这些。参数里的 dispatch, getState 就是我们在 map 的时候,调用 middleware 方法,传进来的 middlewareAPI 。所以我们知道了 chain 的值是一个数组,数组的每一项是调用每个中间件之后的返回函数

我们再来看 dispatch 这一行发生了什么。这里有一个 compose 方法,来看一下源码:

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  const last = funcs[funcs.length - 1]
  const rest = funcs.slice(0, -1)
  return function (...args) {
      rest.reduceRight(function(composed, f) {
      f(composed)
    }, last(...args))
  }
}

compose 类似于 ArrayreduceRight 方法的处理方式,从数组最后一个数组依次向前处理。 如果不太熟悉,看下这个例子就会很快明白:

/**
 * [description]
 * @param  {[type]} previousValue [前一个项]
 * @param  {[type]} currentValue  [当前项]
 */
[0, 1, 2, 3, 4].reduceRight(function(previousValue, currentValue, index, array) {
  return previousValue + currentValue;
}, 10);

以10为初始值,从数组的最后一位数字向左依次累加。所以结合上面的代码,可以知道 compose(...chain) 的运行结果是函数数组 chain最右边的元素开始,带上 store.dispatch 参数执行后依次作为前面一个函数的参数,类似下面这样:

A = function () {};
B = function () {};
C = function () {};

chain = [A, B, C];
//dispatch = compose(...chain)(store.dispatch)
dispatch = A(B(C(store.dispatch)))

明白了 compose 方法,我们就假设只有一个中间件,dispatch 的值就等于:

function(next) {
  return function(action) {
      return typeof action === 'function' ?
          action(dispatch, getState) :
          next(action);
  }
}(store.dispatch)

也就是说,其实 next 参数就等于 store.dispatch 。而此时, dispatch 就等于:

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

我们结合 redux-thunk 的用法来分析这个中间件是如何运行的。

// 异步的 action
function incrementAsync() {
  return dispatch => {
    setTimeout(() => {
      dispatch(increment());
    }, 1000);
  };
}

触发上面异步 action 的方式是:

dispatch(incrementAsync());

回想上面的代码,dispatch 方法是接收一个 action 参数的函数,而这里的 action 就是 incrementAsync() ,进入 dispatch 方法之后,就是:

return typeof action === 'function' ?
    action(dispatch, getState) :
    next(action);

action 的值为 function 时,就调用这个 function ,并且把 dispatch, getState 传给这个 function ,如果不是 function ,就直接 store.dispatch(action) (如上面所讲,next的值就是 store.dispatch )。

那这是只有一个中间件的情况,有多个中间件时,next 就是下一个中间件,一直到调用到最后一个中间件为止。(脑袋已变成一锅粥/(ㄒoㄒ)/~~)

小结

回到我们最开始讲到的,redux 的中间件其实就是让我们可以介入到 actionreducer 之间的过程,我们可以把这个过程理解成主干和分支的概念,redux 默认的同步数据流就是主干,中间件就是分支,主干和分支的分水岭从这里时出现:

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

    return enhancer(createStore)(reducer, preloadedState) // 进入中间件分支
}

当中间件分支处理完 store 以后,就又回到了主干。这种方式其实是使用了装饰者模式,通过不同的中间件对 createStore 进行修饰,形成最后的新的 createStore 方法,这样一来,通过这个方法创建的 store 就拥有了中间件的处理结果。过程的确是比较绕的,但把源码和中间件的用法结合起来看的话,其实也就不难理解了。

    原文作者:Horve大叔
    原文地址: https://segmentfault.com/a/1190000011743094
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞