redux源码解析之applyMiddleware

前言

项目中一直使用redux来进行状态管理,对于redux中间件传递这一块一直不是很理解,只懂得使用不懂得原理,今天来分析一下applyMiddleware的源码。

加载中间件的加载

首先我们来看一下在项目中是如何加载中间件的

import { createStore, applyMiddleware } from 'redux';
imoprt thunk from 'redunx-thunk';
import todos from './reducers';

const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
const store = createStoreWithMiddleware(todos,[ 'Use Redux' ]);

如果你用过redux就会知道,加载中间件会使用到redux提供的工具方法:applyMiddleware,将你需要使用的中间件作为参数传递给applyMiddleware,它会返回一个函数,然后再将createStore传递进,最后得到一个新的createStore;

下面我们来看一下applyMiddleware的源码(可通过gitHub获取)

export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    const store = createStore(...args)
    let dispatch = () => {
      throw new Error(
        `Dispatching while constructing your middleware is not allowed. ` +
          `Other middleware would not be applied to this dispatch.`
      )
    }
    let chain = [] //用于存放中间件

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

    return {
      ...store,
      dispatch
    }
  }
}

代码非常少,20行左右,下面我们就开始分析代码吧

createStore => (...args) =>{....}

我们知道applyMiddleware返回的是一个函数,从使用的方法可以看到这里createStore,就是我们一开始传递进去的那个createStore,它又返回一个函数,这个就是上面说的createStoreWithMiddleware,简单来说,最后调用的是

(reducer,initState)=>{
  const store = createStore(reducer,initState);
}
let dispatch = () => {
      throw new Error(
        `Dispatching while constructing your middleware is not allowed. ` +
          `Other middleware would not be applied to this dispatch.`
      )
    }
let chain = []

这几行注释已经说得很清楚了,就不细说了

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

这里声明了一个变量middlewareAPI,它是一个对象,有两个方法(从这里开始非常的绕,我已经有点晕了。使用了大量的高阶函数)
1.getState 这个方法是对store中的getStat方法的引用
2.dispatch 从上面可以看到已经定义了一个dispatch,规定不允许在加载中间时调用,在下面又重新修改了指向。(表示对store.dispatch的封装)

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

这行代码对传递进行的中间件数组进行一次map,map中调用中间件,并将middlewareAPI作为参数传递给中间件,这里我们就拿redux-thunk来看看中间件是长什么样子的

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

我们通常使用redux-thunk来进行ajax操作,在上面看到进行map操作时将middlewareAPI传递到了redux-thunk,然后它返回了一个函数,用es5表示就是下面这样子的

funciton thunkMiddleware(middlewareAPI){
  return function(next){ 
      return function(action){
         typeof action === 'function' ?
         action(dispatch, getState) :
         next(action);     
     }
  }
}
我们在回到上面
chain = middlewares.map(middleware => middleware(middlewareAPI))

那么现在我们可以知道,这个东西返回了一个像这样的函数

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

在接着往下看之前

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

在说这里之前,我们先看一下compose的源码

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }
  if (funcs.length === 1) {
    return funcs[0]
  }
  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

之前说到chain是一个组数,那么chain === funcs,这个方法首先判断chain数组的长度,如果为0就就返回一个什么都不做的函数(为0就意味着没有中间件),如果直接一个中间件就返回这个中间件并调用它,并且将store.dispatch传递进去,并赋值给dispatch

return funcs.reduce((a, b) => (...args) => a(b(...args))

我们重点来看这行代码,reduce是数组的的一个方法,它的第一个参数是一个函数,这个函数接受4个参数,分别是previousValu(上一次的值),currentValue(当前值),currentIndex(当前值的索引),array;reduce 为数组中的每一个元素依次执行回调函数(不包括数组中被删除或从未被赋值的元素)。

这里的args就是外面传递的store.dispatch;这段代码写成es5的形式大概是下面这样(...agrs是es6语法,如果有不清楚的请参阅# ECMAScript 6 入门)

funcs.reduce(function(a,b){
  retrurn function(... args]){
    return a(b(... args))
  }
})

从代码中可以看出(注意两个...ags,代表的含义不同,第一个代表的是剩余参数,第二个agrs代表的是展开数组),对每个元素执行了回调函数,它又再次返回了一个新的函数(真的无力,嵌套太深了)

return a(b(... args))

继续来看这里,这里是依次执行了中间件,并将返回的函数传递给下一个中间件
如果有数组[fn1,fn2,fn3,fn4],那么返回 fn1(fn2(fn3(fn4(..args))))
还记得thunk的代码吗,这里我们还是拿thunk来举例,看下面代码

 return ({ dispatch, getState }) => next => action => { 
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }
    return next(action);
  };

如果只有一个中间件,那么这里的next表示的是store.dispatch,否则表示其它的中间件。
总的来说,如果我们只使用一个中间,那么最后得到的dispatch方法就是

action => { 
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }
    return next(action);
  };

你可能感兴趣的:(redux源码解析之applyMiddleware)