Vue.js - 使用Vuex进行状态管理详解6(modules模块化)
作者:hangge | 2026-10-08 08:39
Vuex 通过"单一状态树"来管理应用层级的全部状态。然而,当 store 中的状态数据变得越来越多时,会难以维护和管理。为了解决这个问题,我们可以使用 modules 将状态数据模块化。本文我将详细介绍 modules 的基本使用、局部状态、命名空间、辅助函数以及动态注册等内容。




一、基本介绍
1,为什么需要模块化?
(1)在使用 Vuex 的单一状态树模式下,应用的所有状态都集中在一个大的对象中。随着应用规模的增长,会出现以下问题:
- store 文件变得臃肿:所有状态、mutations、actions、getters 都写在一个文件中
- 难以维护:代码量大,逻辑复杂,难以定位问题
- 协作困难:多人开发时容易产生代码冲突
(2)modules 提供的解决方案:
- 将 store 分割成多个模块(module)
- 每个模块拥有自己的 state、mutation、action、getter
- 支持嵌套子模块,形成树形结构
2,模块的基本结构
一个典型的模块结构如下所示:
提示:模块的 state 使用函数形式(state: () => ({ ... }))定义,这与 Vue 组件的 data 选项类似,可以避免状态被多个实例共享。
// 模块 A
const moduleA = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... },
getters: { ... }
}
// 模块 B
const moduleB = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... }
}
// 根模块
const store = createStore({
modules: {
a: moduleA, // 模块 A 被注册为 a
b: moduleB // 模块 B 被注册为 b
}
})
// 访问模块的状态
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
二、modules 的基本使用
1,创建子模块
(1)下面我们为 Vuex 的 store 增加 home 和 user 两个子模块。首先在 src/store 文件夹下新建 modules 文件夹,然后在其中新建 home.js 和 user.js 文件。

(2)home.js 子模块代码:
const homeModule = {
state() {
// home 模块的 state
return {
// 在 home 模块中定义一个 homeCounter 全局变量
homeCounter: 100
}
},
getters: {
// getters 定义...
},
mutations: {
// mutations 定义...
},
actions: {
// actions 定义...
}
}
export default homeModule
(3)user.js 子模块代码:
const userModule = {
state() {
// user 模块的 state
return {
// 在 user 模块中定义一个 userCounter 全局变量
userCounter: 1000
}
},
getters: {
// getters 定义...
},
mutations: {
// mutations 定义...
},
actions: {
// actions 定义...
}
}
export default userModule
2,注册子模块
修改 src/store/index.js 文件,将 home 和 user 两个子模块引入根模块:
提示:modules 对象中的 key 会成为模块的名称,存储到子模块中的数据依然可以实现全局共享。
import { createStore } from 'vuex'
import home from './modules/home'
import user from './modules/user'
const store = createStore({
state() {
// 根模块 state
return {
counter: 0, // 根模块的变量
discount: 0.9, // 书籍打9折
}
},
mutations: {
// 定义 increment 函数,可获取 state 对象
increment(state) {
state.counter++
},
},
getters: {
// 获取当前的折扣
currentDiscount(state) {
return state.discount
},
},
actions: {
// 根模块 actions...
},
// 引入 home 和 user 两个子模块
modules: {
home: home, // key 指定模块的名称,value 指定引入的模块
user, // ES6 简写语法,相当于 user: user
},
})
export default store
3,在组件中访问模块状态
(1)创建一个组件,分别访问根模块和子模块的状态,具体代码如下:
- 通过 $store.state 获取根模块的 state 对象
- 通过 $store.state.home 获取 home 子模块的 state 对象
- 通过 $store.state.user 获取 user 子模块的 state 对象
<template>
<div>
<h4>root state 根模块的状态: {{ $store.state.counter }}</h4>
<h4>home state 子模块的状态: {{ $store.state.home.homeCounter }}</h4>
<h4>user state 子模块的状态: {{ $store.state.user.userCounter }}</h4>
</div>
</template>
<script>
export default {}
</script>
(2)运行效果:

三、modules 的局部状态
1,局部状态的概念
子模块定义的 state 属于子模块的状态,称为局部状态。以下是子模块的一些特点:
- 在子模块内部,mutation 和 getter 函数接收的第一个参数 state 是局部状态
- 在子模块内部,对于 action 函数,局部状态可以通过 context.state 暴露出来,根节点状态则通过 context.rootState 暴露出来
- 在子模块内部,getter 函数的根节点状态(rootState)会作为第三个参数暴露出来
2,局部状态的使用示例
(1)下面修改 user.js 子模块,展示局部状态的使用方式:
注意:在子模块内不要直接修改 rootState,应该通过提交 mutation 来更新状态。
const userModule = {
state() {
// state 属于 user 模块的状态,称为局部状态
return {
userCounter: 1000
}
},
getters: {
// user 模块的 getter 函数接收的第一个参数是局部状态 (state)
doubleUserCount(state) {
return state.userCounter * 2
},
// user 模块的 getter 函数,根节点状态 (rootState) 作为第三个参数暴露出来
userCountAddRootCount(state, getters, rootState) {
return state.userCounter + rootState.counter
}
},
mutations: {
increment(state) {
// state 是 user 模块的局部状态
state.userCounter++
}
},
actions: {
// 在 action 函数中,局部状态通过 context.state 暴露出来
// 根节点状态通过 context.rootState 暴露出来
incrementAction({ state, commit, rootState }) {
commit('increment')
}
}
}
export default userModule
(2)创建一个组件来展示 user 子模块的局部状态:
<template>
<div>
<h4>root state 根模块的状态: {{ $store.state.counter }}</h4>
<h4>root state 根模块 currentDiscount: {{ $store.getters.currentDiscount }}</h4>
<!-- 访问子模块的 state 和 getters -->
<h4>user state 子模块 userCounter 的状态: {{ $store.state.user.userCounter }}</h4>
<h4>user state 子模块 doubleUserCount: {{ $store.getters.doubleUserCount }}</h4>
<h4>user state 子模块 userCountAddRootCount: {{ $store.getters.userCountAddRootCount }}</h4>
<button @click="incrementAction">+1</button>
</div>
</template>
<script>
import { useStore } from 'vuex'
export default {
setup() {
const store = useStore()
const incrementAction = () => {
// 会触发 root 模块和 user 子模块的 incrementAction 函数
store.dispatch('incrementAction')
}
return { incrementAction }
}
}
</script>
(3)运行效果如下,当点击"+1"按钮时,会分发 incrementAction,这会同时触发根模块和 user 子模块中的 incrementAction 函数。这是因为默认情况下,模块内的 action、mutation 和 getter 仍然注册在全局命名空间中。

四、modules 命名空间
1,什么是命名空间?
(1)默认情况下,子模块内部的 action、getter 和 mutation 仍然注册在全局的命名空间中,这使得多个模块可以对同一个 action 或 mutation 作出响应。
(2)为了解决这个问题,可以通过添加 namespaced: true 使其成为带有命名空间的模块:
- 模块内的 getter、action 和 mutation 都会自动根据模块注册的路径调整命名
- 使用时需要添加模块名称前缀,如 store.dispatch('home/incrementAction')
2,命名空间的使用样例
(1)修改 home.js 子模块,添加 namespaced: true:
提示:添加 namespaced: true 后,该模块的 getter、action 和 mutation 都会自动添加模块名前缀。
const homeModule = {
namespaced: true, // 为 home 模块添加命名空间
state() {
return {
homeCounter: 100
}
},
getters: {
doubleHomeCount(state) {
return state.homeCounter * 2
},
homeCountAddRootCount(state, getters, rootState) {
return state.homeCounter + rootState.counter
}
},
mutations: {
increment(state) {
state.homeCounter++
}
},
actions: {
incrementAction({ state, commit, rootState }) {
commit('increment')
}
}
}
export default homeModule
(2)创建一个组件,展示如何访问带命名空间的模块,具体代码如下:
- 访问 getters 时使用数组语法:$store.getters['home/doubleHomeCount']
- 分发 action 时需要添加模块前缀:store.dispatch('home/incrementAction')
<template>
<div>
<h4>root state 根模块的状态: {{ $store.state.counter }}</h4>
<!-- 访问子模块的 state 和 getters(需要添加模块前缀)-->
<h4>home 子模块 homeCounter 的状态: {{ $store.state.home.homeCounter }}</h4>
<h4>home 子模块 doubleHomeCount: {{ $store.getters['home/doubleHomeCount'] }}</h4>
<h4>home 子模块 homeCountAddRootCount: {{ $store.getters['home/homeCountAddRootCount'] }}</h4>
<button @click="incrementAction">+1</button>
</div>
</template>
<script>
import { useStore } from 'vuex'
export default {
setup() {
const store = useStore()
const incrementAction = () => {
// 只会触发 home 模块的 incrementAction 函数
store.dispatch('home/incrementAction')
}
return { incrementAction }
}
}
</script>
(3)运行效果如下,可用看到现在点击"+1"按钮只会触发 home 子模块的 incrementAction 函数。

五、带命名空间的模块访问全局内容
1,访问根模块的 state 和 getters
如果希望在子模块中使用根 state 和 getters,可以通过以下方式:
- getter 函数:第三个和第四个参数分别是 rootState 和 rootGetters
- action 函数:通过 context 对象的 rootState 和 rootGetters 属性访问
2,访问全局的 action 和 mutation
在子模块中,如果想要分发全局命名空间内的 action 或提交 mutation,只需将 { root: true } 作为第三个参数传递:
提示:在带命名空间的模块内,dispatch 和 commit 默认作用于当前模块。添加 { root: true } 可以访问根模块。
const homeModule = {
namespaced: true,
state() {
return {
homeCounter: 100
}
},
getters: {
// home 模块: state, getters
// 根模块: rootState, rootGetters
homeCountAddRootCount(state, getters, rootState, rootGetters) {
return state.homeCounter + rootState.counter
}
},
actions: {
// home 模块: state, commit, dispatch, getters
// 根模块: rootState, rootGetters
incrementAction({ state, commit, dispatch, getters, rootState, rootGetters }) {
commit('increment') // 提交到当前模块的 mutation
commit('increment', null, { root: true }) // 提交到根模块的 mutation
dispatch('incrementAction', null, { root: true }) // 分发到根模块的 action
}
}
}
export default homeModule
3,注册全局 action
若需要在带命名空间的模块中注册全局 action,可以添加 root: true,并将这个 action 的定义放在 handler 函数中:
const homeModule = {
namespaced: true,
actions: {
// 这是一个全局 action,不带模块前缀
someAction: {
root: true,
handler(namespacedContext, payload) {
// someAction 会被注册到全局命名空间
}
}
}
}
六、modules 辅助函数
1,方式一:映射时指定模块名前缀
在使用带命名空间的模块时,可以通过在映射时直接添加模块名前缀来简化代码:
<template>
<div>
<h4>home 子模块 homeCounter 的状态: {{ homeCounter }}</h4>
<h4>home 子模块 doubleHomeCount: {{ doubleHomeCount }}</h4>
<button @click="homeIncrementCommit">+1</button>
<button @click="incrementAction">+1</button>
</div>
</template>
<script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
export default {
computed: {
// 方式一:mapState 映射
...mapState({
homeCounter: state => state.home.homeCounter
}),
// 方式一:mapGetters 映射
...mapGetters({
doubleHomeCount: 'home/doubleHomeCount'
})
},
methods: {
// 方式一:mapMutations 映射
...mapMutations({
homeIncrementCommit: 'home/increment'
}),
// 方式一:mapActions 映射
...mapActions({
incrementAction: 'home/incrementAction'
})
}
}
</script>
2,方式二:辅助函数第一个参数作为模块名前缀
可以将模块名前缀作为辅助函数的第一个参数:
<script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
export default {
computed: {
// 方式二:将模块名作为第一个参数
...mapState('home', ['homeCounter']),
...mapGetters('home', ['doubleHomeCount'])
},
methods: {
// 方式二:将模块名作为第一个参数
...mapMutations('home', {
homeIncrementCommit: 'increment'
}),
...mapActions('home', ['incrementAction'])
}
}
</script>
3,方式三:使用 createNamespacedHelpers(推荐)
Vuex 提供了 createNamespacedHelpers 函数,可以创建基于某个命名空间的辅助函数:
提示:这是最推荐的方式,代码简洁且易于维护。创建的辅助函数自动绑定到指定的命名空间。
<script>
import { createNamespacedHelpers } from 'vuex'
// 方式三:使用 createNamespacedHelpers 创建绑定到 home 模块的辅助函数
const { mapState, mapGetters, mapMutations, mapActions } = createNamespacedHelpers('home')
export default {
computed: {
// 不需要再写模块前缀
...mapState(['homeCounter']),
...mapGetters(['doubleHomeCount'])
},
methods: {
...mapMutations({
homeIncrementCommit: 'increment'
}),
...mapActions(['incrementAction'])
}
}
</script>
4,方式四:在 setup 中使用(Composition API)
(1)在 Composition API 的 setup 中,可以结合 createNamespacedHelpers 和自定义 Hook 使用:
<script>
import { computed } from 'vue'
import { createNamespacedHelpers, useStore } from 'vuex'
// 创建绑定到 home 模块的辅助函数
const { mapState, mapGetters, mapMutations, mapActions } = createNamespacedHelpers('home')
export default {
setup() {
const store = useStore()
// 手动创建计算属性
const homeCounter = computed(() => store.state.home.homeCounter)
const doubleHomeCount = computed(() => store.getters['home/doubleHomeCount'])
// 获取 mutations 和 actions
const mutationFuncs = mapMutations({
homeIncrementCommit: 'increment'
})
const actionsFuncs = mapActions(['incrementAction'])
return {
homeCounter,
doubleHomeCount,
...mutationFuncs,
...actionsFuncs
}
}
}
</script>
(2)为了简化 mapState 和 mapGetters 在 setup 中的使用,可以封装一个 useMapper 工具函数:
// src/hooks/useMapper.js
import { computed } from 'vue'
import { useStore } from 'vuex'
export function useMapper(mapFn, mapper) {
// 1. 获取 store 对象
const store = useStore()
// 2. 获取映射的结果
const storeStateFns = mapFn(mapper)
// 3. 将普通函数转换为计算属性函数
const storeState = {}
Object.keys(storeStateFns).forEach(fnKey => {
const fn = storeStateFns[fnKey].bind({ $store: store })
storeState[fnKey] = computed(fn)
})
return storeState
}
(3)使用封装好的 useMapper 函数:
<script>
import { createNamespacedHelpers } from 'vuex'
import { useMapper } from '../hooks/useMapper'
const { mapState, mapGetters, mapMutations, mapActions } = createNamespacedHelpers('home')
export default {
setup() {
// 方式四:使用自定义 Hook
const stateFunc = useMapper(mapState, ['homeCounter'])
const gettersFunc = useMapper(mapGetters, ['doubleHomeCount'])
const mutationFuncs = mapMutations({ homeIncrementCommit: 'increment' })
const actionsFuncs = mapActions(['incrementAction'])
return {
...stateFunc,
...gettersFunc,
...mutationFuncs,
...actionsFuncs
}
}
}
</script>
附一:模块动态注册
1,动态注册模块
(1)在 store 创建之后,可以使用 store.registerModule 方法动态注册模块:
import { createStore } from 'vuex'
const store = createStore({ /* 选项 */ })
// 注册模块 `myModule`
store.registerModule('myModule', {
state: () => ({ ... }),
mutations: { ... },
actions: { ... },
getters: { ... }
})
// 注册嵌套模块 `nested/myModule`
store.registerModule(['nested', 'myModule'], {
// ...
})
(2)注册后可以通过 store.state.myModule 和 store.state.nested.myModule 访问模块的状态。
提示:模块动态注册功能使得其他 Vue 插件可以通过在 store 中附加新模块的方式来使用 Vuex 管理状态。
2,动态卸载模块
使用 store.unregisterModule(moduleName) 方法动态卸载模块:
// 卸载动态注册的模块
store.unregisterModule('myModule')
注意:不能使用此方法卸载静态模块(即创建 store 时声明的模块)。
3,检查模块是否已注册
使用 store.hasModule(moduleName) 方法检查模块是否已被注册:
// 检查模块是否已注册
if (store.hasModule('myModule')) {
console.log('myModule 已注册')
}
// 嵌套模块需要使用数组形式
if (store.hasModule(['nested', 'myModule'])) {
console.log('nested/myModule 已注册')
}
4,保留 state
在注册新模块时,如果想保留过去的状态(例如从服务端渲染的应用保留 state),可以使用 preserveState 选项:
store.registerModule('a', module, { preserveState: true })
附二:最佳实践
1,模块划分原则
(1)按照功能或业务领域划分模块,例如:
- user:用户相关状态(登录信息、权限等)
- products:商品相关状态
- cart:购物车相关状态
- app:应用全局状态(主题、语言等)
(2)推荐的项目目录结构:
src/
└── store/
├── index.js # 主 store 文件
└── modules/
├── user.js # 用户模块
├── products.js # 商品模块
├── cart.js # 购物车模块
└── app.js # 应用全局模块
2,始终使用命名空间
(1)建议为所有模块添加 namespaced: true:
const myModule = {
namespaced: true, // 始终添加命名空间
state: () => ({ ... }),
// ...
}
(2)好处:
- 避免不同模块之间的命名冲突
- 提高模块的封装性和可复用性
- 代码更清晰,易于维护
3,使用常量定义类型
在大型项目中,建议使用常量代替 mutation 和 action 类型字符串:
// mutation-types.js
export const USER_SET_INFO = 'user/SET_INFO'
export const USER_LOGOUT = 'user/LOGOUT'
// user.js
import { USER_SET_INFO, USER_LOGOUT } from '../mutation-types'
const userModule = {
namespaced: true,
mutations: {
[USER_SET_INFO](state, payload) {
// ...
}
}
}
全部评论(0)