Vue 3源码深度解析之:`Vuex`的`Module`:`Mutation`和`Action`的命名空间化。

各位老铁,早上好!今天咱们聊聊Vuex里头的Module,重点是Mutation和Action的命名空间。这玩意儿听起来高大上,其实就是为了解决大型项目里“变量名冲突”和“模块划分”的问题。想象一下,一个几百人的团队,都往同一个地方写代码,变量不小心重名了,那酸爽… 咱们Vuex的命名空间就是来避免这种“事故”发生的。

一、Module:Vuex的模块化管理

首先,得搞清楚Module是个啥。简单来说,Module就是把Vuex的statemutationsactionsgetters打包成一个独立的“模块”,这样可以更好地组织和管理你的状态。

// 假设我们有一个用户模块
const userModule = {
  state: () => ({
    name: '张三',
    age: 30,
  }),
  mutations: {
    setName(state, newName) {
      state.name = newName;
    },
    setAge(state, newAge) {
      state.age = newAge;
    }
  },
  actions: {
    updateName({ commit }, newName) {
      commit('setName', newName);
    },
    updateAge({ commit }, newAge) {
      commit('setAge', newAge);
    }
  },
  getters: {
    profile(state) {
      return `${state.name},${state.age}岁`;
    }
  }
};

// 然后,把这个模块注册到Vuex里
const store = new Vuex.Store({
  modules: {
    user: userModule  // 注册一个名为'user'的模块
  }
});

现在,你就可以通过store.state.user.name来访问用户的名字了。但是,如果你的项目只有一个模块,那命名空间就显得有点多余。但当项目越来越大,模块越来越多,命名空间就显得尤为重要。

二、命名空间:告别“变量名撞车”的烦恼

命名空间的作用,说白了就是给你的mutationsactions加上一个“前缀”,防止不同的模块里的mutationsactions重名。

要启用命名空间,只需要在模块定义里加上namespaced: true即可。

const userModule = {
  namespaced: true, // 启用命名空间
  state: () => ({
    name: '张三',
    age: 30,
  }),
  mutations: {
    setName(state, newName) {
      state.name = newName;
    },
    setAge(state, newAge) {
      state.age = newAge;
    }
  },
  actions: {
    updateName({ commit }, newName) {
      commit('setName', newName);
    },
    updateAge({ commit }, newAge) {
      commit('setAge', newAge);
    }
  },
  getters: {
    profile(state) {
      return `${state.name},${state.age}岁`;
    }
  }
};

const store = new Vuex.Store({
  modules: {
    user: userModule
  }
});

三、调用命名空间模块里的Mutation和Action

启用了命名空间后,调用mutationsactions的方式就稍微有点变化了。

  1. 在组件里调用:mapMutationsmapActions

    如果你的组件需要调用mutationsactions,可以使用mapMutationsmapActions辅助函数。

    <template>
      <div>
        <p>姓名:{{ name }}</p>
        <button @click="changeName">改名字</button>
      </div>
    </template>
    
    <script>
    import { mapState, mapMutations, mapActions } from 'vuex';
    
    export default {
      computed: {
        ...mapState('user', ['name']) // 第一个参数是模块名
      },
      methods: {
        ...mapMutations('user', ['setName']), // 第一个参数是模块名
        ...mapActions('user', ['updateName']), // 第一个参数是模块名
        changeName() {
          // this.setName('李四'); // 错误!因为有命名空间了
          this.updateName('李四');
        }
      }
    };
    </script>

    注意到mapStatemapMutationsmapActions的第一个参数都是模块名'user'。这告诉Vuex,你要映射的是user模块里的状态、mutations和actions。

  2. 直接通过store.commitstore.dispatch调用

    如果你不想用mapMutationsmapActions,也可以直接通过store.commitstore.dispatch调用,但是需要加上模块名前缀。

    // 在组件里
    this.$store.commit('user/setName', '王五'); // 加上模块名前缀
    this.$store.dispatch('user/updateName', '赵六'); // 加上模块名前缀
    
    // 在Action里
    actions: {
      someAction({ commit }) {
        commit('user/setName', '孙七'); // 加上模块名前缀
      }
    }

    注意,在使用store.commitstore.dispatch时,mutation和action的名字前面要加上模块名/

四、Action里的rootStaterootGetters

在启用了命名空间的Action里,有时候你可能需要访问全局的state或者getters。这时候,你可以通过rootStaterootGetters来实现。

const userModule = {
  namespaced: true,
  state: () => ({
    name: '张三',
    age: 30,
  }),
  actions: {
    // 访问全局的state和getters
    someAction({ state, commit, rootState, rootGetters }) {
      console.log('user module state:', state.name);
      console.log('global state:', rootState.count); // 假设全局state里有个count
      console.log('global getter:', rootGetters.doubleCount); // 假设全局getters里有个doubleCount
    }
  }
};

const store = new Vuex.Store({
  state: () => ({
    count: 10
  }),
  getters: {
    doubleCount(state) {
      return state.count * 2;
    }
  },
  modules: {
    user: userModule
  }
});

// 在组件里
store.dispatch('user/someAction');

rootStaterootGetters让你可以在模块内部访问到全局的状态和getters,这在某些场景下非常有用。

五、dispatchcommit的第三个参数:{ root: true }

有时候,你可能需要在模块内部触发全局的actions或者mutations。这时候,你可以在dispatchcommit的第三个参数里加上{ root: true }

const userModule = {
  namespaced: true,
  state: () => ({
    name: '张三',
    age: 30,
  }),
  actions: {
    // 触发全局的action
    someAction({ dispatch }) {
      dispatch('increment', null, { root: true }); // 触发全局的increment action
    }
  }
};

const store = new Vuex.Store({
  state: () => ({
    count: 10
  }),
  mutations: {
    increment(state) {
      state.count++;
    }
  },
  actions: {
    increment({ commit }) {
      commit('increment');
    }
  },
  modules: {
    user: userModule
  }
});

// 在组件里
store.dispatch('user/someAction');

加上{ root: true }后,Vuex会认为你要触发的是全局的actions或者mutations,而不是当前模块的。

六、源码分析:命名空间是如何实现的?

咱们稍微深入一点,看看Vuex源码里是怎么实现命名空间的。这部分稍微有点烧脑,但理解了之后,你会对Vuex的运行机制有更深刻的认识。

Vuex在注册模块的时候,会递归地处理模块的结构。对于启用了命名空间的模块,Vuex会给mutationsactions的名字加上模块名前缀。

简单来说,Vuex内部会有一个函数,大概长这样:

function registerModule(store, rawModule, path, runtime) {
  const namespaced = !!rawModule.namespaced; // 判断是否启用了命名空间

  // 处理mutations
  for (const type in rawModule.mutations) {
    const namespacedType = namespaced ? namespace(path, type) : type; // 如果启用了命名空间,加上模块名前缀
    registerMutation(store, namespacedType, rawModule.mutations[type], local);
  }

  // 处理actions
  for (const type in rawModule.actions) {
    const namespacedType = namespaced ? namespace(path, type) : type; // 如果启用了命名空间,加上模块名前缀
    registerAction(store, namespacedType, rawModule.actions[type], local);
  }

  // 递归处理子模块
  for (const moduleName in rawModule.modules) {
    registerModule(store, rawModule.modules[moduleName], path.concat(moduleName), runtime);
  }
}

// 生成命名空间的函数
function namespace(path, key) {
  return path.length > 0 ? path.join('/') + '/' + key : key;
}

这段代码只是一个简化版的示意,真正的Vuex源码要复杂得多。但核心思想就是:如果模块启用了命名空间,Vuex会在注册mutationsactions的时候,给它们的名字加上模块名前缀。

七、总结

好了,今天咱们就聊到这里。总结一下:

  • Module是Vuex的模块化管理工具,可以将state、mutations、actions、getters打包成独立的模块。
  • 命名空间可以防止不同模块里的mutations和actions重名。
  • 启用命名空间后,需要使用mapMutationsmapActions,或者在store.commitstore.dispatch时加上模块名前缀。
  • 在命名空间的Action里,可以通过rootStaterootGetters访问全局的state和getters。
  • 可以通过{ root: true }在模块内部触发全局的actions或者mutations。

希望今天的讲解对你有所帮助。记住,命名空间是大型Vuex项目里不可或缺的一部分,它可以让你的代码更清晰、更易于维护。咱们下期再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注