当 Vue 项目业务越来越复杂时,全局store会变得庞大且难以维护 ------Vuex 的Modules(模块化) 功能可以将store按业务拆分为多个子模块,每个模块独立管理自己的state、mutations、actions、getters,大幅提升代码的可维护性。
一、为什么要用 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')
这个报错会导致页面渲染中断,而 ?. 可以安全访问嵌套属性:
- 当
userInfo为null/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后,不同模块的方法不会重名; - 团队协作:不同开发者可以维护不同模块,降低冲突风险。