Vuex 进阶,模块化开发(Modules):解决单状态树臃肿问题

当 Vue 项目业务越来越复杂时,全局store会变得庞大且难以维护 ------Vuex 的Modules(模块化) 功能可以将store按业务拆分为多个子模块,每个模块独立管理自己的statemutationsactionsgetters,大幅提升代码的可维护性。

一、为什么要用 Vuex Modules?

Vuex 是单状态树(所有数据存在一个state中),随着业务增长:

  • store会变得越来越大,代码耦合度高;
  • 不同业务的mutations/actions容易重名;
  • 团队协作时难以拆分工作。

而 Modules 可以将store按业务(如 "用户模块""购物车模块")拆分,每个模块拥有独立的状态管理逻辑。

二、Vuex Modules 的基础使用

1. 步骤 1:创建子模块文件

src/store/modules目录下创建子模块(以 "用户模块" 为例):

javascript 复制代码
// src/store/modules/user.js
export default {
  // 开启命名空间(关键!实现模块隔离)
  namespaced: true,
  
  // 模块内的state
  state: {
    userInfo: null, // 用户信息
    token: ''       // 登录凭证
  },
  
  // 模块内的mutations
  mutations: {
    SET_USER_INFO(state, userInfo) {
      state.userInfo = userInfo;
    },
    SET_TOKEN(state, token) {
      state.token = token;
    }
  },
  
  // 模块内的actions
  actions: {
    async login({ commit }, form) {
      // 模拟接口请求
      const res = await new Promise(resolve => {
        setTimeout(() => {
          resolve({ 
            userInfo: { name: '张三', age: 20 }, 
            token: 'fake_token_123' 
          });
        }, 1000);
      });
      commit('SET_USER_INFO', res.userInfo);
      commit('SET_TOKEN', res.token);
      return res;
    }
  },
  
  // 模块内的getters
  getters: {
    isLogin(state) {
      return !!state.token; // 判断是否登录
    }
  }
};

2. 步骤 2:在 store 中注册子模块

src/store/index.js中引入并注册子模块:

javascript 复制代码
// src/store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
import user from './modules/user'; // 引入用户模块

Vue.use(Vuex);

export default new Vuex.Store({
  // 注册子模块(key为模块名,value为模块对象)
  modules: {
    user
  }
});

三、开启命名空间(namespaced: true

默认情况下,子模块的mutations/actions/getters会被挂载到全局命名空间,无法实现模块隔离。

必须在模块中添加namespaced: true,才能让模块拥有独立的命名空间,避免不同模块的方法重名。

四、模块内核心成员的使用方式

开启命名空间后,模块内的state/mutations/actions/getters需要通过模块名访问。

1. 访问模块内的 state

方式 1:直接通过$store.state.模块名访问(不推荐)

vue 复制代码
<template>
  <!-- 直接访问:$store.state.模块名.数据 -->
  <div>用户名:{{ $store.state.user.userInfo?.name }}</div>
</template>

方式 2:通过mapState辅助函数(推荐)

vue 复制代码
<script>
import { mapState } from 'vuex';
export default {
  computed: {
    // 格式:...mapState('模块名', ['要映射的state数据'])
    ...mapState('user', ['userInfo', 'token'])
  }
};
</script>

<template>
  <div>用户名:{{ userInfo?.name }}</div>
  <div>Token:{{ token }}</div>
</template>

userInfo 初始值是 null(或接口请求未返回时也是 undefined),如果直接写 userInfo.name,会触发 JavaScript 报错:

plaintext 复制代码
Cannot read properties of null (reading 'name')

这个报错会导致页面渲染中断,而 ?. 可以安全访问嵌套属性:

  • userInfonull/undefined 时,userInfo?.name 会直接返回 undefined,不会报错;
  • userInfo 有值时,正常读取 name 属性。

2. 调用模块内的 mutations

方式 1:直接通过$store.commit('模块名/函数名')调用

vue 复制代码
<script>
export default {
  methods: {
    logout() {
      // 格式:$store.commit('模块名/mutations函数名', 参数)
      this.$store.commit('user/SET_TOKEN', '');
    }
  }
};
</script>

方式 2:通过mapMutations辅助函数(推荐)

vue 复制代码
<script>
import { mapMutations } from 'vuex';
export default {
  methods: {
    // 格式:...mapMutations('模块名', ['要映射的mutations函数'])
    ...mapMutations('user', ['SET_TOKEN']),
    logout() {
      this.SET_TOKEN(''); // 直接调用映射后的方法
    }
  }
};
</script>

3. 调用模块内的 actions

方式 1:直接通过$store.dispatch('模块名/函数名')调用

vue 复制代码
<script>
export default {
  async onLogin() {
    const form = { username: 'zhangsan', password: '123456' };
    // 格式:$store.dispatch('模块名/actions函数名', 参数)
    const res = await this.$store.dispatch('user/login', form);
    console.log('登录结果:', res);
  }
};
</script>

方式 2:通过mapActions辅助函数(推荐)

vue 复制代码
<script>
import { mapActions } from 'vuex';
export default {
  methods: {
    // 格式:...mapActions('模块名', ['要映射的actions函数'])
    ...mapActions('user', ['login']),
    async onLogin() {
      const form = { username: 'zhangsan', password: '123456' };
      const res = await this.login(form); // 直接调用映射后的方法
      console.log('登录结果:', res);
    }
  }
};
</script>

4. 访问模块内的 getters

方式 1:直接通过$store.getters['模块名/getter名']访问(不推荐)

vue 复制代码
<template>
  <!-- 直接访问:$store.getters['模块名/getter名'] -->
  <div>是否登录:{{ $store.getters['user/isLogin'] ? '是' : '否' }}</div>
</template>

方式 2:通过mapGetters辅助函数(推荐)

vue 复制代码
<script>
import { mapGetters } from 'vuex';
export default {
  computed: {
    // 格式:...mapGetters('模块名', ['要映射的getters'])
    ...mapGetters('user', ['isLogin'])
  }
};
</script>

<template>
  <div>是否登录:{{ isLogin ? '是' : '否' }}</div>
</template>

五、Vuex Modules 的优势总结

  • 拆分代码 :按业务拆分store,避免单状态树臃肿;
  • 隔离命名 :开启namespaced后,不同模块的方法不会重名;
  • 团队协作:不同开发者可以维护不同模块,降低冲突风险。
相关推荐
掘金安东尼5 小时前
纯 CSS 实现弹性文字效果
前端·css
牛奶5 小时前
Vue 基础理论 & API 使用
前端·vue.js·面试
牛奶5 小时前
Vue 底层原理 & 新特性
前端·vue.js·面试
anOnion5 小时前
构建无障碍组件之Radio group pattern
前端·html·交互设计
pe7er6 小时前
状态提升:前端开发中的状态管理的设计思想
前端·vue.js·react.js
SoaringHeart6 小时前
Flutter调试组件:打印任意组件尺寸位置信息 NRenderBox
前端·flutter
晚风予星7 小时前
Ant Design Token Lens 迎来了全面升级!支持在 .tsx 或 .ts 文件中直接使用 Design Token
前端·react.js·visual studio code
sunny_7 小时前
⚡️ vite-plugin-oxc:从 Babel 到 Oxc,我为 Vite 写了一个高性能编译插件
前端·webpack·架构
GIS之路7 小时前
ArcPy 开发环境搭建
前端
林小帅9 小时前
【笔记】OpenClaw 架构浅析
前端·agent