恋の歌的logo
分类 归档 标签

Redux源码解读 - applyMiddleware篇

发表于2020-07-10
更新于2023-02-13
分类于编程
总字数1.7K
阅读时长 ≈6 分钟

Redux源码解读 - 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,
      };
    };
}

刚开始我以为这函数这么短应该会很简单。

但是到后面和createStore函数结合的时候我才发现比createStore难多了。

主要是函数嵌套会有点晕(套,就嗯套)。

先说说这个东西的作用。

有些时候,我们想在每一次的状态变化之前,变化之后处理一些和业务无关的逻辑操作,比如写写日志。

那么我们可能会这样写。

javascript
const store = //...
  // 某个业务
  function fn() {
    console.log(store.getState());
    store.dispatch({ type: TYPE });
    console.log(store.getState());
  };

fn();

但是这样写有一个问题,就是他耦合了业务函数fn

试想下如果我们在100个业务函数中都这样写,有问题吗?

写当然没有问题,但是如果某一天要求在dispatch前打印下时间,那就完蛋了,要改100个地方,而且这100个函数还分布在不同的文件里。

正所谓,复制黏贴一时爽,需求一变火葬场。

所以applyMiddleware这个API就是为了解决这样的问题而出现的。

middleware单词意思为中间件,简单点讲就是可以通过外部代码来增强内部的逻辑。

如果以我们的想法,可能会这么写:

javascript
const store = //...

const dispatch = store.dispatch;

store.dispatch = (action) => {
  console.log(store.getState());
  const newAction = store.dispatch(action);
  console.log(store.getState());
  return newAction;
}

store.dispatch({type:TYPE});

这就是增强dispatch方法来实现额外的逻辑,不会耦合在业务里面。

applyMiddleware也是通过增强dispatch来实现额外的逻辑。

不过applyMiddleware支持多中间件,这个可以从它的参数看出来。

举个使用applyMiddleware的小例子,就以状态改变前后输出日志为例。

javascript
const reducer = (state = 0, action) => {
  switch (action.type) {
    case "add":
      return state + 1;
    case "delete":
      return state - 1;
    default:
      return state;
  }
};

// 定义了一个打印日志的中间件
const loggerMiddleware = (middlewareAPI) => {
  return (dispatch) => {
    return (action) => {
      console.log("logger before");
      console.log("state is " + middlewareAPI.getState());
      let newAction = dispatch(action);
      console.log("logger before");
      console.log("state is " + middlewareAPI.getState());
      return newAction;
    };
  };
};

const store = createStore(
  reducer,
  // 用`applyMiddleware`包裹中间件和reducer一起传入createStore中
  applyMiddleware(loggerMiddleware),
);

store.dispatch({
  type: "add",
});

当我们执行上面的代码时,会打印:

当然也可以用两个中间件:

javascript
const reducer = (state = 0, action) => {
  switch (action.type) {
    case "add":
      return state + 1;
    case "delete":
      return state - 1;
    default:
      return state;
  }
};

// 定义了一个打印日志的中间件
const loggerMiddleware = (middlewareAPI) => {
  return (dispatch) => {
    return (action) => {
      console.log("logger before");
      console.log("state is " + middlewareAPI.getState());
      let newAction = dispatch(action);
      console.log("logger before");
      console.log("state is " + middlewareAPI.getState());
      return newAction;
    };
  };
};

// 定义了一个打印时间的中间件
const timeMiddleware = (middlewareAPI) => {
  return (dispatch) => {
    return (action) => {
      console.log("time");
      let newAction = dispatch(action);
      return newAction;
    };
  };
};

const store = createStore(
  reducer,
  // 用`applyMiddleware`包裹中间件和reducer一起传入createStore中
  applyMiddleware(loggerMiddleware, timeMiddleware),
);

store.dispatch({
  type: "add",
});

运行之后就会出现:

ok,开始分析源码。

applyMiddleware函数传入中间件的数组,然后直接返回了一个函数。

返回的函数传入了一个createStore,也就是我们上篇帖子说的函数。

javascript
return (createStore) =>
  (...args) => {
    // 主体逻辑
  };

源代码这样写可能看着晕,完全可以把它拆开。

javascript
return (createStore) => {
  return (...arg) => {
    // 主体逻辑
  };
};

主题的逻辑都在我上面标的位置。

javascript
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.",
  );
};

先是通过传入的createStore创建函数,和第二层的arg参数创建了一个store

定义了一个初始的dispatch函数。

javascript
const middlewareAPI = {
  getState: store.getState,
  dispatch: (...args) => dispatch(...args),
};

接下来定义了一个middlewareAPI,对应我们创建中间件时最外层函数的参数

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

接着处理中间件数组,把中间件的API传进去,也就是解开了一层函数的包装

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

这里我觉得就是这个函数最难理解的一段了。

逻辑上就是合并(compose)已经给定API的中间件数组,提供一个最原始的dispatch,返回一个包装过的dispatch

可以先看一下compose这个函数。

javascript
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)),
  );
}

这段代码中最重要的就是最后一句funcs.reduce((a, b) => (...args) => a(b(...args)))

使用到reduce这个API的时候,我很喜欢举一个例子,就是累加:

javascript
const sum = [1, 2, 3].reduce((sum, val) => sum + val);
console.log(sum);

上面这段就会输出数字6啦。

回到原来的函数,箭头函数如果套太深其实看着确实晕,所以把它拆出来。

javascript
funcs.reduce((a, b) => {
  return (...args) => {
    return a(b(...args));
  };
});

这里我们用三个中间件来表示这一个过程。

PS:这里已经没有最外层的middlewareAPI为参数的函数了,因为上一句的map操作已经把这一层解开了。

javascript
const m1 = (dispatch) => {
  return (action) => {};
};

const m2 = (dispatch) => {
  return (action) => {};
};

const m3 = (dispatch) => {
  return (action) => {};
};

我们知道reduce如果没有传入第二个参数作为默认值的话,是会选择数组的第一项作为初始值,然后跳过第一次循环。

所以在第二次循环的时候。

a就为m1b就为m2,返回值作为下一次的a

第三次的循环的时候。

a就为:

javascript
a = (...args) => {
  return m1(m2(args));
};

bm3,返回值作为下一次的a

那么最后返回的就是第四次的a

此时a就为:

javascript
a = (...args) => {
  return ((...args) => {
    return m1(m2(args));
  })(m3(args));
};

m3(args)返回了一个新的dispatch

然后依次是m2(args)返回新的dispatch

最后是m1(args)返回新的dispatch

最后m1返回的dispatch作为了新的storedispatch

也就是最后的代码:

javascript
return {
  ...store,
  dispatch,
};

而且可以从这个嵌套看出来,越后面的中间件他的执行距离真正的dispatch会更近。

也就是我们之前使用了两个中间件loggerMiddlewaretimeMiddleware

传入的时候是先loggerMiddlewaretimeMiddleware

输出的时候就是先loggerMiddlewaretimeMiddleware

text
loggerMiddleware在dispatch之前的输出
timeMiddleware在dispatch之前的输出
真正的dispatch
timeMiddleware在dispatch之后的输出
loggerMiddleware在dispatch之后的输出

最后就是在createStore中在传入了中间件的情况下直接返回。

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

  // 传入了自己
  return enhancer(createStore)(reducer, preloadedState);
}

这里需要注意一个点

就是MiddlewareAPI中的dispatch是整个storedispatch

而构造的中间件函数中的dispatch参数是指包装了剩下中间件的dispatch

一般我们会把这个dispatch写为next,这样就更便于理解了。

javascript
const loggerMiddleware = (middlewareAPI) => {
  return (next) => {
    return (action) => {
      console.log("logger before");
      console.log("state is " + middlewareAPI.getState());
      let newAction = next(action);
      console.log("logger before");
      console.log("state is " + middlewareAPI.getState());
      return newAction;
    };
  };
};
哦呐该,如果没有评论的话,瓦达西...