Redux中间件源码剖析

redux中间件

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

redux 的中间件是经过第三方插件的方式实现,自己源码也不是不少,咱们就从源码来解读 redux 的中间件机制。java

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

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 中间件的运行原理究竟是怎么样的。express

applyMiddleware

首先看一下 applyMiddleware 的源码:redux

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 其实就是:app

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

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

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

接下来就来看看 createStore 作了什么。异步

createStore

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

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 就拥有了中间件的处理结果。过程的确是比较绕的,但把源码和中间件的用法结合起来看的话,其实也就不难理解了。

相关文章
相关标签/搜索