vuex 踩坑分享-registerModule

2,325 阅读2分钟

使用 registerModule 多次注册同一模块会导致后面再调用此模块下的 action 时连续触发多次

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

// 根模块
const store = new Vuex.Store({});

// 子模块-简便起见我们这里只定义个actions
const moduleA = {
  actions: {
    inc() {
      console.log("i was called");
    },
  },
};

// 多次注册子模块
store.registerModule("moduleA", moduleA);
store.registerModule("moduleA", moduleA);

// 调用一次 action
store.dispatch("inc");

实际上 action 被执行了两次

i was called
i was called

这个问题的出现原因是通过 registerModule 注册模块的时候并不会检查模块本身是否注册过,而 vuex 内部又是通过数组来收集 action 的,这就导致多次注册同一模块时 action 被加入到内部数组中多次。action 在 vuex 中被注册的简化代码如下:

module.forEachAction((action, key) => {
  const type = action.root ? key : namespace + key;
  const handler = action.handler || action;
  registerAction(store, type, handler, local);
});

function registerAction(store, type, handler, local) {
  const entry = store._actions[type] || (store._actions[type] = []);
  entry.push(function wrappedActionHandler(payload) {
    // action参数注入等逻辑
  }
}

这里的 action 之所以是数组是 vuex 作者考虑到可能不同的模块需要对某一 action 做出响应,也就是说默认情况下不同模块的 action 是可以重名的,且默认注册在根命名空间下。比如说我们有个应用的不同页面被分成不同模块,然后用户切换账号的时候我们需要将不同页面上展示的资源都刷新掉,这时候就可以注册个名字叫做 refreshResource 的 action,各个模块可以实现自己的定制刷新逻辑,然后一次触发全部刷新(mutation 默认也是注册在根命名空间下)。当然,也可以为 module 添加 namespaced 属性控制 vuex 不要将 action 注册在根命名空间下。

添加了 namespaced 属性的模块可以在 action 中添加 root 属性将 action 和 mutation 注册在根命名空间下

const moduleA = {
  namespaced: true,
  actions: {
    foo: {
      root: true,
      handler(namespacedContext, payload) {
        // ...
      },
    },
  },
};

那我们如何避免模块重复注册的问题呢?思路其实也挺简单的,那就是我们在注册模块前先判断下模块是否已经注册过,如果已经注册过就不再注册了

const store = new Vuex.Store({
  // ...
});
rewriteRegisterModule(store);

function rewriteRegisterModule(store) {
  const original = store.registerModule;
  store.registerModule = function(path, ...rest) {
    if (store.hasModule(path)) {
      return;
    }
    original.call(store, path, ...rest);
  };
}