模块化和命名空间
# 模块化和命名空间
在大型应用中,Vuex 的 store
可能会变得非常庞大且难以维护。为了让代码更易于管理和维护,Vuex 提供了模块化和命名空间的机制。
# 1. 模块化的好处
- 提高可维护性:将
store
分成多个独立的模块,各自管理自己的state
、mutations
、actions
和getters
。这样可以使每个模块专注于特定的业务逻辑,降低复杂度。 - 增强代码可读性:模块化的结构使得代码层次分明,易于理解。每个模块的职责清晰明确,便于新开发者快速上手。
- 避免命名冲突:通过命名空间,确保模块内的命名不冲突。这种方式不仅提高了模块的独立性,也保证了代码的可维护性和扩展性。
- 便于团队协作:不同开发人员可以专注于不同的模块,减少相互之间的干扰,提高开发效率
使用命名空间的主要目的
通过为每个模块定义独立的命名空间,可以避免不同模块之间的状态、变更方法、操作方法和计算属性的命名冲突。例如,两个模块中都有 count
状态,通过命名空间,可以区分为 moduleA/count
和 moduleB/count
。
# 2. 修改 store.js
在 store.js
中,将 store
拆分为多个模块,每个模块都有自己的 state
、mutations
、actions
和 getters
,并通过 namespaced: true
开启命名空间。
import Vue from 'vue'; // 引入 Vue 框架
import Vuex from 'vuex'; // 引入 Vuex 库
Vue.use(Vuex); // 将 Vuex 安装为 Vue 插件
// countAbout 模块
const countAbout = {
namespaced: true, // 开启命名空间
state: { x: 1 }, // 模块的 state
mutations: {
// 模块的 mutations
JIA(state, value) {
state.x += value; // 修改 state 中的 x
}
},
actions: {
// 模块的 actions
jia(context, value) {
context.commit('JIA', value); // 提交 JIA mutation
}
},
getters: {
// 模块的 getters
bigSum(state) {
return state.x * 10; // 计算 x 的十倍
}
}
};
// personAbout 模块
const personAbout = {
namespaced: true, // 开启命名空间
state: { list: [] }, // 模块的 state
mutations: {
// 添加一个人到列表中
ADD_PERSON(state, person) {
state.list.push(person); // 向 list 中添加一个 person
}
},
actions: {
// 添加名为 Wang 的人
addPersonWang(context, person) {
if (person.name === 'Wang') {
context.commit('ADD_PERSON', person); // 提交 ADD_PERSON mutation
}
}
}
};
// 创建并导出 Vuex Store 实例
const store = new Vuex.Store({
modules: {
countAbout, // 注册 countAbout 模块
personAbout // 注册 personAbout 模块
}
});
export default store; // 导出 store 实例
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# 3. 访问模块化 state
笔记
模块化以后,你只需要在原来的 mapState
、mapGetters
、mapActions
和 mapMutations
的语法之前加上对应的模块名称就可以了。这样可以确保你访问和操作的是指定模块中的状态、计算属性、动作和变更。
在组件中,访问模块化的 state
有两种方式:
直接访问
state
:// 直接通过路径访问模块的 state this.$store.state.personAbout.list; // 访问 personAbout 模块的 list
1
2使用
mapState
辅助函数:import { mapState } from 'vuex'; computed: { ...mapState('countAbout', ['x']) // 将 countAbout 模块的 x 映射为计算属性 }
1
2
3
4
5
# 4. 访问模块化 getters
直接访问
getters
:// 通过路径和名称访问模块的 getters this.$store.getters['countAbout/bigSum']; // 访问 countAbout 模块的 bigSum getter
1
2使用
mapGetters
辅助函数:import { mapGetters } from 'vuex'; computed: { ...mapGetters('countAbout', ['bigSum']) // 将 countAbout 模块的 bigSum 映射为计算属性 }
1
2
3
4
5
# 5. 调用模块化 actions
直接调用
dispatch
:// 使用路径和名称直接调度模块的 actions this.$store.dispatch('personAbout/addPersonWang', person); // 调用 personAbout 模块的 addPersonWang action
1
2使用
mapActions
辅助函数:import { mapActions } from 'vuex'; methods: { ...mapActions('countAbout', { incrementOdd: 'jiaOdd', // 将 countAbout 模块的 jiaOdd action 映射为 incrementOdd 方法 incrementWait: 'jiaWait' // 将 countAbout 模块的 jiaWait action 映射为 incrementWait 方法 }) }
1
2
3
4
5
6
7
8
# 6. 调用模块化 mutations
直接调用
commit
:// 使用路径和名称直接提交模块的 mutations this.$store.commit('personAbout/ADD_PERSON', person); // 调用 personAbout 模块的 ADD_PERSON mutation
1
2使用
mapMutations
辅助函数:import { mapMutations } from 'vuex'; methods: { ...mapMutations('countAbout', { increment: 'JIA', // 将 countAbout 模块的 JIA mutation 映射为 increment 方法 decrement: 'JIAN' // 将 countAbout 模块的 JIAN mutation 映射为 decrement 方法 }) }
1
2
3
4
5
6
7
8
# 7. 模块化文件结构
在大型 Vue.js 应用中,随着业务逻辑的增加,单一的 store
文件可能会变得复杂且难以管理。通过模块化可以将 store
分解为多个独立的模块,每个模块管理自己独立的 state
、mutations
、actions
和 getters
。这种方式不仅提高了代码的可读性和可维护性,还使得开发过程更为清晰易懂。
# 1. 模块化文件的优势
- 结构更清晰:模块化文件使得代码结构更加清晰,每个文件只关注特定的功能和业务逻辑,从而使得整个项目的组织更为合理。
- 易于维护:由于每个模块相对独立,在某个模块中进行变更或修复不会影响到其他模块,降低了整体维护成本。
- 提高复用性:通过模块化可以更方便地在项目中复用代码,不同模块可以独立开发、测试和维护。
- 协作开发:在团队开发中,模块化允许多个开发者同时在不同模块中工作,而不会互相干扰,提高了开发效率。
# 2. 创建模块化文件结构
将 store
的每个模块分拆为单独的文件存储在 modules
目录中。在 store/index.js
中引入这些模块,以便集成到 Vuex store
中。
文件目录结构
src/
└── store/
├── index.js // 主 store 配置文件
├── modules/ // 存储各个子模块
│ ├── countAbout.js // countAbout 模块
│ └── personAbout.js// personAbout 模块
2
3
4
5
6
# 子模块1countAbout.js
// store/modules/countAbout.js
// 定义模块的状态
const state = {
x: 1 // 模块的初始状态值
};
// 定义模块的变更方法
const mutations = {
// 增加 x 的值
JIA(state, value) {
state.x += value; // 修改 state.x 的值
}
};
// 定义模块的操作方法
const actions = {
// 触发 JIA mutation
jia({ commit }, value) {
commit('JIA', value); // 提交 JIA mutation 来修改 state
}
};
// 定义模块的计算属性
const getters = {
// 计算属性 bigSum,返回 x 的十倍
bigSum(state) {
return state.x * 10; // 返回 state.x 的十倍值
}
};
// 导出模块
export default {
namespaced: true, // 启用命名空间
state,
mutations,
actions,
getters
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# 子模块2personAbout.js
// store/modules/personAbout.js
// 定义模块的状态
const state = {
list: [] // 存储人员列表的状态
};
// 定义模块的变更方法
const mutations = {
// 向列表中添加新人员
ADD_PERSON(state, person) {
state.list.push(person); // 将新人员添加到 list 数组中
}
};
// 定义模块的操作方法
const actions = {
// 添加名为 "Wang" 的人员
addPersonWang({ commit }, person) {
if (person.name === 'Wang') {
commit('ADD_PERSON', person); // 如果名字是 Wang,提交 ADD_PERSON mutation
}
}
};
// 导出模块
export default {
namespaced: true, // 启用命名空间
state,
mutations,
actions
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 3. 注册子模块到 store/index.js
# 手动注册模块
对于模块数量较少或不使用自动化工具的项目,可以手动注册子模块到主 store
配置文件。
import Vue from 'vue'; // 引入 Vue 框架
import Vuex from 'vuex'; // 引入 Vuex 库
import countAbout from './modules/countAbout'; // 导入 countAbout 模块
import personAbout from './modules/personAbout'; // 导入 personAbout 模块
Vue.use(Vuex); // 将 Vuex 安装为 Vue 插件
// 创建并导出 Vuex store 实例
const store = new Vuex.Store({
modules: {
countAbout, // 注册 countAbout 模块到 Vuex Store
personAbout // 注册 personAbout 模块到 Vuex Store
}
});
export default store; // 导出 store 实例
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 使用 Webpack 自动注册模块
在 Vue 项目中,Webpack 提供了 require.context
API,可以实现自动化模块注册。这对于使用 Webpack 构建的项目非常有用。
import Vue from 'vue'; // 引入 Vue 框架
import Vuex from 'vuex'; // 引入 Vuex 库
Vue.use(Vuex); // 将 Vuex 安装为 Vue 插件
// 使用 Webpack 的 require.context 方法自动加载 ./modules 目录下的所有以 .js 结尾的模块文件
const modulesFiles = require.context('./modules', true, /\.js$/);
// 遍历加载的模块文件,将每个模块的导出对象收集到一个名为 modules 的对象中
const modules = modulesFiles.keys().reduce((modules, modulePath) => {
// 获取模块名称 './app.js' => 'app'
const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1');
const value = modulesFiles(modulePath);
modules[moduleName] = value.default;
return modules;
}, {});
// 创建一个 Vuex store 实例,传入之前收集到的模块对象
const store = new Vuex.Store({
modules // 注册所有模块
});
export default store; // 导出 store 实例
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
require.context:Webpack 提供的 API,用于创建一个上下文模块,可以批量引入某个目录下的模块。
- 第一个参数:要搜索的目录 (
./modules
)。 - 第二个参数:一个布尔值,表示是否也查询其子目录 (
true
)。 - 第三个参数:一个正则表达式,匹配文件名 (
/\.js$/
)。
- 第一个参数:要搜索的目录 (
modulesFiles.keys():返回匹配的模块路径数组。
reduce():遍历每个模块路径,加载模块并将其添加到
modules
对象中。moduleName:去掉路径和文件扩展名后的模块名称。
value.default:每个模块的默认导出对象。
# 使用 Vite 自动注册模块
在 Vue 3 项目中,如果使用 Vite 作为构建工具,可以利用 Vite 提供的 import.meta.glob
来实现模块的自动化注册。
import { createStore } from 'vuex'; // Vuex 4 在 Vue 3 中使用
// 使用 import.meta.glob 来批量导入模块
const modulesFiles = import.meta.glob('./modules/*.js');
// 遍历加载的模块,并将其添加到 modules 对象中
const modules = {};
for (const path in modulesFiles) {
modulesFiles[path]().then((module) => {
const moduleName = path.replace(/^\.\/modules\/(.*)\.\w+$/, '$1');
modules[moduleName] = module.default;
});
}
// 创建并导出 Vuex store 实例
const store = createStore({
modules // 注册所有模块
});
export default store; // 导出 store 实例
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import.meta.glob
:Vite 提供的 API,用于批量导入匹配指定模式的模块。- 模式:指定要导入的文件(
'./modules/*.js'
表示导入modules
目录下所有.js
文件)。
- 模式:指定要导入的文件(
modulesFiles[path]()
:返回一个Promise
,通过then
方法异步加载模块。module.default
:获取模块的默认导出对象,并将其添加到modules
对象中。