首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Redux(七):源码分析之applyMiddleware

一、源码分析

Redux真正强大之处便是中间件系统,利用各类自定义或第三方的中间件,可以极大丰富其功能,派发的action也将不再局限于普通纯对象。

还记得createStore里enhancer参数吗?在createStore.js的53行,如果传递了enhancer,则:

代码语言:javascript
复制
return enhancer(createStore)(reducer, preloadedState)

接着看下边的实例:

代码语言:javascript
复制
const initialState = {
  todos:[]
};
const reducer = function(state,action){
  switch (action.type){
    case "TODO_ADD":
      return Object.assign({},state,{
        todos:state.todos.concat({text:action.text})
      })
  }
  return state;
};
const enhancer = function(createStore){
  return function(reducer, preloadedState){
    return createStore(reducer,preloadedState);
  }
};
const store = createStore(reducer,initialState,applyMiddleware(enhancer));

可以看出来,这里store是执行enhancer的返回函数所创建的。其实enhancer就是applyMiddleware的返回结果,我们来看看applyMiddleware的源码:

代码语言:javascript
复制
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.`
      )
    }

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

    return {
      ...store,
      dispatch
    }
  }
}

这个看起来不够直观,转成ES5,那么就是这样的:

代码语言:javascript
复制
function applyMiddleware(...middlewares){
  return function(createStore){
    return function(reducer,preloadedState){
      const store = createStore(reducer,preloadedState);
      let dispatch = function(){
        throw new Error(
          `Dispatching while constructing your middleware is not allowed. ` +
          `Other middleware would not be applied to this dispatch.`
        )
      };
      const middlewareAPI = {
        getState: store.getState,
        dispatch:function(...args){
          return dispatch(...args);
        }
      };
      const chain = middlewares.map(function(middleware){
        return middleware(middlewareAPI);
      });
      dispatch = compose(...chain)(store.dispatch);
      return {
        ...store,
        dispatch
      }
    }
  }
}

对比我们的enhancer函数,是不是很像呢,接着逐行分析最里面的代码。

first step:

代码语言:javascript
复制
const store = createStore(reducer,preloadedState);
let dispatch = function(){
  throw new Error(
    `Dispatching while constructing your middleware is not allowed. ` +
    `Other middleware would not be applied to this dispatch.`
  )
};

创建store,自定义dispatch错误函数,这个函数的意思:不能在中间件里面直接调用根dispatch,应该调用形参dispatch。

second step:

代码语言:javascript
复制
const middlewareAPI = {
  getState: store.getState,
  dispatch:function(...args){
    return dispatch(...args);
  }
};
const chain = middlewares.map(function(middleware){
  return middleware(middlewareAPI);
});

定义middlewareAPI对象,包含getState和dispatch俩个属性。

定义chain数组,每个元素是middleware的执行结果(依然是函数),参数就是middlewareAPI。在中间件里面不能直接调用middlewareAPI的dispatch。

third step:

代码语言:javascript
复制
dispatch = compose(...chain)(store.dispatch);
return {
  ...store,
  dispatch
}

结合上一章compose的解读,那么中间件的执行顺序就是由后向前的,执行队列的每个dispatch形参都由上个中间件的执行结果返回。

二、自定义中间件格式

结合上边的分析,那么一个最最简单的中间件应该是这种格式的。

代码语言:javascript
复制
function middleware(middlewareAPI){
  return function(next){
    return function(action){
      next(action);
    }
  }
}

我们定义俩个中间件:

代码语言:javascript
复制
function middleware1(middlewareAPI){
  return function ware1(next){
    return function(action){
      console.log("middleware1开始");
      next(action);
      console.log("middleware1结束");
    }
  }
}
function middleware2(middlewareAPI){
  return function ware2(next){
    return function(action){
      console.log("middleware2开始");
      next(action);
      console.log("middleware2结束");
    }
  }
}

结合上边的源码,那么在applyMiddlewares中数组常量chain的格式就是这样的:

代码语言:javascript
复制
const chain = [
  function ware1(next){
    return function(action){
      console.log("middleware1开始");
      next(action);
      console.log("middleware1结束")
    }
  },
  function ware2(dispatch){
    return function(next){
      console.log("middleware2开始");
      next(action);
      console.log("middleware2结束")
    }
  },
];

接着经过compose函数的运算,所以原始的dispatch会作为ware2中间件的参数,然后ware2的返回值会作为ware1的参数,那么最终dispatch函数就是这样子的:

代码语言:javascript
复制
const dispatch = function(action){
  console.log("middleware1开始");
  void function(next){
    console.log("middleware2结束");
    next(action);
    console.log("middleware2结束")
  }(next)
  console.log("middleware1结束")
};

那么当派发一个action之后,正确的执行顺序就是:

  1. 打印middleware1开始
  2. 打印middleware2开始
  3. 执行原始的dispatch函数
  4. 执行subscribe注册的回调函数
  5. 打印middleware2结束
  6. 打印middleware1结束

总结

综上,调用next之前可以获取到当前state树,调用结束后可以获取到更新后的state树,基本中间件格式:

代码语言:javascript
复制
function middleware(middlewareAPI){
  return function(next){
    return function(action){
      console.log("middleware开始",middlewareAPI.getState());
      next(action);
      console.log("middleware结束",middlewareAPI.getState());
    }
  }
}

同时中间件的引用顺序也是有讲究的,第一个中间的action回传给redux必须是纯对象。

举报
领券