模块(Module)是什么
Vuex在简单应用中的使用相信大家都会,但是当应用变得复杂时,store对象就可能变得臃肿,这时可以使用module解决这个问题。
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
模块(Module)动态注册
在前面的官方示例代码中,moduleA和moduleB都是预先知道的,那如果module无法预知呢?
需求:提供一个具有基础功能的版本,其他团队在此基础版上添加团队的定制功能
在这种需求下,Vuex的module设计采用动态注册(store.registerModule)是比较合适的。
// store.js
export default new Vuex.Store({
})
// components/base.vue 基础功能组件
<script>
import '../modules/base'
export default {
...
};
</script>
// modules/base.js 基础功能module
import baseStore from '../store'
let store = {
state: {},
mutations: {},
actions: {
init() {
console.log('baseModule:init')
}
},
getters: {}
}
baseStore.registerModule('base', store)
// components/team1.vue 团队1定制功能组件
<script>
import '../modules/team1'
export default {
...
};
</script>
// modules/team1.js 团队1定制功能module
import baseStore from '../store'
let store = {
state: {},
mutations: {},
actions: {
init() {
console.log('team1Module:init')
}
},
getters: {}
}
baseStore.registerModule('team1', store)
// 类似还有 team2和team3...
补充知识点一:卸载模块
你也可以使用 store.unregisterModule(moduleName) 来动态卸载模块。注意,你不能使用此方法卸载静态模块(即创建 store 时声明的模块)。
补充知识点二:保留state
在注册一个新 module 时,你很有可能想保留过去的 state,例如从一个服务端渲染的应用保留 state。你可以通过 preserveState 选项将其归档:store.registerModule(‘a’, module, { preserveState: true })。
当你设置 preserveState: true 时,该模块会被注册,action、mutation 和 getter 会被添加到 store 中,但是 state 不会。这里假设 store 的 state 已经包含了这个 module 的 state 并且你不希望将其覆写。
模块(Module)命名空间
问题:当基础功能和团队定制功能包含相同名称的action、mutation 和 getter时,会同时被触发
// components/base.vue
this.$store.dispatch('init')
//输出
baseModule:init
team1Module:init
team2Module:init
team3Module:init
默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。
把store打印出来,对上面这句话会有更直观的感受
如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。
// components/base.vue
// !!!注意:有命名空间时action名称需要加上registerModule时注册的模块名
this.$store.dispatch('base/init')
// modules/base.js 基础功能module
import baseStore from '../store'
let store = {
namespaced: true,
...
}
// 有命名空间时dispatch时需要带上这里传入的'base'前缀
baseStore.registerModule('base', store)
// modules/team1.js 团队1定制功能module
import baseStore from '../store'
let store = {
namespaced: true,
...
}
// 同样,有命名空间时,dispatch时需要带上这里传入的'team1'前缀
baseStore.registerModule('team1', store)
// components/base.vue
this.$store.dispatch('base/init')
//输出
baseModule:init
再来看下store的打印
模块(module)访问全局命名空间
需求:基础功能和定制功能需要获取权限信息进行管理
获取权限是个通用的功能,放在全局命名空间是比较合适的
//store.js
export default new Vuex.Store({
actions:{
getPermission() {
console.log('request permission')
}
}
})
若需要在全局命名空间内分发 action 或提交 mutation,将 { root: true } 作为第三参数传给 dispatch 或 commit 即可。
// modules/base.js 基础功能module
import baseStore from '../store'
let store = {
...
actions: {
init({dispatch}) {
console.log('baseModule:init')
dispatch('getPermission',null,{root:true})
}
}
}
// modules/team1.js 团队1定制功能module
import baseStore from '../store'
let store = {
...
actions: {
init({dispatch}) {
console.log('team1Module:init')
dispatch('getPermission',null,{root:true})
}
}
}
store的插件机制
需求:对所有模块的dispatch接口调用进行数据统计
可以通过store的插件机制暴露出每个action的钩子。
//store.js
const myPlugin = store => {
store.subscribeAction((action, state) => {
console.log( 'report:', action.type)
})
}
export default new Vuex.Store({
plugins: [myPlugin],
...
})
补充知识点:subscribeAction 是在2.5.0新增的。mutation也是可以通过 subscribe 订阅的。
Vuex Module
Vuex API参考
关于本文
作者:@红烧牛肉面
原文:https://github.com/masterkong/blog/issues/11
热 文 推 荐
☞ 7个有用的Vue开发技巧
☞ Serverless掀起新的前端技术变革
☞ GitHub上的项目受美国出口管制吗?
☞ Webpack 是怎样运行的?
☞ 面试加分项之 Nginx 反向代理与负载均衡
你也“在看”吗?