本篇只是本人对 module 的学习总结和理解以及一些避免坑的意见,不必定能够用在你的项目当中,即便你要使用,建议你先参考官方对比下先后文。
另外,module 是基于 vuex 即 store 状态的模块化管理方案,因此本篇是针对有过 store 使用经验的同窗的一篇仅供参考的我的总结,若是你还不会 store 你得抓紧了! 或者你能够参考 大宏说
老师的《Vuex白话教程第六讲:Vuex的管理员Module(实战篇)》javascript
模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象:css
const moduleA = {
state: { count:10 },
getters:{
filterCount(state){
return state.count * 2; // 20;
}
},
mutations:{
add(state){
state.count * 2; // 20;
}
},
}
复制代码
action则是经过context.state
暴露出来:html
actions:{
incrementIfOddOnRootSum(context){
context.state.count // 10
}
}
复制代码
action 能够经过rootState
获取到根节点的状态:vue
actions:{
incrementIfOddOnRootSum( { state, rootState } ){
rootState.xx // 根节点的xx
}
}
复制代码
getter 接受根节点状态是经过第三个参数暴露出来:java
getters:{
sumWithRootCount(state, getters, rootState){
//state 是模块内部的状态
// getters 模块内部的其余getter
// rootState 是全局的状态
}
}
复制代码
若是模块不使用命名空间的话,默认状况下模块内部的 getter, action 和 mutation是注册在全局全局命名空间的,这样的坏处是:web
store:{
state:{
count:18,
},
mutations:{
setCount(state){
state.count / 2;
console.log(state.count) // 9
}
},
modules:{
a:moduleA
}
}
moduleA:{
state:{
count:10,
},
mutations:{
setCount(state){
state.count * 2;
console.log(state.count)//20
}
}
}
复制代码
在提交 moduleA 的 mutation
时:vuex
this.$store.commit('setCount');
// 猜猜会打印啥?
// 9 和 20 这是由于前面所说的模块内部的getter,action和mutation是注册在全局全局命名空间的。
//因此上面的例子中全局命名空间里有2个名为setCount的mutation,而后他们都被触发了。
复制代码
想要让模块内部的 getter, mutation , action只做用域当前局部模块内的话能够给模块添加namespaced
属性:api
modules:{
moduleA:{
namespaced:true,
state:{...}, //state仍是仅做用于当前模块内部
getter:{
isAdmin(){...}
},
mutations:{
login(){...}
},
actions:{
getToken(){...}
}
}
}
复制代码
当开启命名空间的模块被注册后它的全部 getter、action 及 mutation 都会自动根据模块注册的路径调整命名,因此触发路径也有所改变:bash
store.getters['moduleA/isAdmin']; // 命名空间模块内部的getter
store.dispatch('moduleA/getToken'); // 命名空间模块内部的action
store.commit('moduleA/login'); // 命名空间模块内部的mutation
复制代码
文档里也有说模块内是还能够嵌套模块的大概意思就是:app
modules:{
moduleA:{
state:{...},
mutations:{...},
// 嵌套模块
modules:{
mypage:{
state:{...},
getters:{
profile(state){}//由于嵌套模块没有本身命名空间,因此就自动继承了父命名空间,因此就能够这样触发这个getter:store.getters['moduleA/profile'];
}
},
// 进一步嵌套命名空间
posts:{
namespaced:true,//开启命名空间
state:{...},
getters:{
popular(){...}//前面咱们说过,开启命名空间的模块它全部的getter、action、mutation都会自动根据模块的路径调整命名 -> store.getters['moduleA/posts/popular']
}
}
}
}
}
复制代码
接着上面继承命名空间的例子:
modules:{
moduleA:{
state:{...},
mutations:{...},
// 嵌套模块
modules:{
mypage:{
state:{...},
getters:{
profile(){...}
}
}
}
}
}
复制代码
若是我如今要触发profile
这个getter我能够这样:
store.getters['moduleA/profile'];
复制代码
由于即便是嵌套的模块但mypage没有本身的命名空间因此继承了父命名空间,因此这样触发看上去没有问题。
问题来了⚠️
若是父命名空间内也有一个名为 profile
的getter:
modules:{
moduleA:{
state:{...},
getters:{
profile(state){...}
}
mutations:{...},
// 嵌套模块
modules:{
mypage:{
state:{...},
getters:{
profile(){...}
}
}
}
}
}
复制代码
这个时候若是再执行:
store.getters['moduleA/profile'];
复制代码
会是什么结果呢?你们能够本身动手试一试,加深一下印象。
若是你但愿使用全局 state 和 getter,rootState 和 rootGetters 会做为第三和第四参数传入 getter,也会经过 context
对象的属性传入 action。
modules: {
foo: {
namespaced: true,
getters:{
someGetter(state, getters, rootState, rootGettrers){
// state 是当前模块内部是状态
// getters 是当前模块内部的 getters
// rootState 是全局下的状态
// rootGettrers 是全局下的 gettrers
}
},
actions:{
someAction( { getters, rootGetters}){
// getters 当前模块内部的 getters,
// rootGettrers 是全局下的 gettrers
}
}
}
}
复制代码
若须要在全局命名空间内分发 action 或提交 mutation,将 { root: true }
做为第三参数传给 dispatch 或 commit 便可:
action:{
someAction( { dispatch, commit}){
dispatch('someOtherAction');// 分发当前模块内名为someOtherAction的action
dispatch('someOtherAction', null, { root: true })// 分发全局名为someOtherAction的action
commit('someMutation') // 提交当前模块内名为someMutation的mutation
commit('someMutation', null, { root: true }) // 提交全局名为someMutation的mutation
}
}
复制代码
是的访问全局只须要提供
rootState
和rootGetters
参数就好,而分发action或提交mutation只须要将{ root: true }
做为第三个参数就好。
若业务须要在带命名空间的模块中注册全局 action,你可添加 root: true
,并将这个 action 的定义放在函数 handler 中。例如:
{
actions: {
someOtherAction ({dispatch}) {
dispatch('someAction')
}
},
modules: {
foo: {
namespaced: true,
actions: {
someAction: {
root: true,
handler (namespacedContext, payload) { ... } // -> 'someAction'
}
}
}
}
}
复制代码
官方给了两种方案,这里我使用命名空间辅助函数的写法,另外一种写法有兴趣的同窗能够去参考一下
这个例子官方其实已经给的很简洁直观了因此咱们直接看:
import { createNamespacedHelpers } from 'vuex'
const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')
export default {
computed: {
// 在 `some/nested/module`上下文中 中查找
...mapState({
a: state => state.a,
b: state => state.b
})
},
methods: {
// 在 `some/nested/module`上下文中 中查找
...mapActions([
'foo',
'bar'
])
}
}
复制代码
这个例子我有一个疑问:若是mapState
和mapActions
是基于'some/nested/module'
上下文的话,那若是我这个组件内还须要使用其余命名空间的模块该怎么办呢?有的同窗可能会说:
const { mapState, mapActions } = createNamespacedHelpers('otherSome/nested/module')
复制代码
再定义一个上下文不就行了吗?但两个上下文返回的都是一样的mapState
和mapActions
,我在使用mapActions
时,你怎么知道我是在那个上下文中查找呢?
…
后来想了想我这个疑问是否成立?由于我以为一个模块store
应该始终是效力于一个功能组件的。可是不保证没有极端的状况出现,若是真有这种需求的话,该怎么实现?有经验的同窗能够教我一下。
若是有业务需求须要咱们动态注册模块,咱们可使用 store.registerModule
方法注册模块:
// 注册模块 `myModule`
store.registerModule('myModule', {
// ...
})
// 注册嵌套模块 `nested/myModule`
store.registerModule(['nested', 'myModule'], {
// ...
})
复制代码
动态注册的前提是你的Store已经被你建立了。
以后就能够经过store.state.myModule
和store.state.nested.myModule
访问模块的状态。
以后不须要模块时可使用store.unregisterModule(moduleName)
来动态卸载模块来保证性能
注意:不能使用此方法卸载静态模块(即建立 store 时声明的模块)
模块重用,官方给的场景是:
一、建立多个store
,他们共用同一个模块
二、在一个store
中屡次注册同一个模块
说的可能比较抽象,咱们来一个简单的例子:
moduleA.js
const moduleA = {
state:{
count:10
},
mutations:{
changeCount(state){
state.count = 20;
}
}
}
export default moduleA;
复制代码
store.js
const store = new Vuex.Store({
state:{...}
mutations:{...},
modules:{
a:moduleA,
b:moduleA,
c:moduleA
}
})
复制代码
此时modules
对象里的a
b
c
都引用自moduleA
模块;
咱们再来建3个组件,而后分别引用a
b
c
这3个模块的实例:
test1.vue
<template>
<div>
{{count}}
</div>
</template>
<script>
import { createNamespacedHelpers } from 'vuex'
const { mapState } = createNamespacedHelpers('a')
export default {
computed: {
...mapState({
name:state => state.count
})
}
}
</script>
复制代码
test2.vue
<template>
<div>
{{count}}
</div>
</template>
<script>
import { createNamespacedHelpers } from 'vuex'
const { mapState } = createNamespacedHelpers('b')
export default {
computed: {
...mapState({
name:state => state.count
})
}
}
</script>
复制代码
test3.vue
<template>
<div>
{{count}}
</div>
</template>
<script>
import { createNamespacedHelpers } from 'vuex'
const { mapState } = createNamespacedHelpers('c')
export default {
computed: {
...mapState({
name:state => state.count
})
}
}
</script>
复制代码
3个组件的count都等于10,由于modulea
b
c
都引用自moduleA
模块;
此时,若是咱们在test1
组件里提交moduleA的mutation
:
test1.vue
<template>
<div>
{{count}}
<input type="button" value="click" @click="changeCount">
</div>
</template>
<script>
import { createNamespacedHelpers } from 'vuex'
const { mapState,mapMutations } = createNamespacedHelpers('a')
export default {
methods:{
...mapMutations([changeCount])
}
computed: {
...mapState({
name:state => state.count
})
}
}
</script>
复制代码
此时,只要咱们一提交changeCount,test1
test2
test3
组件里的count都会被改成20;
缘由:
当一个模块被定义,模块可能被用来建立多个实例,这时若是模块还是一个纯粹的对象,则全部实例将共享引用同一个数据对象!这就是模块间数据互相污染的问题。
为了解决互相污染咱们可使用一个函数声明来返回模块的状态:
const MyReusableModule = {
state () {
return {
count: 10
}
},
// mutation, action 和 getter 等等...
}
复制代码
经过为 state 声明一个初始数据对象的函数,且每次建立一个新实例后,咱们可以调用 state 函数,从而返回初始数据的一个全新副本数据对象。
此番借鉴Vue 组件内的 data
大体意思就是让state以函数声明式返回状态,这样无论模块被实例化多少次,每次实例化时模块内部的state
都会是一个全新的函数返回。
给某些同窗一些建议:作笔记、总结这种东西必定是要你本身先学习一遍,而后理解事后的记录,并不是是把人家文档的东西循序渐进放到你的笔记当中,这样作的意义何在呢?骗点击的沙雕网友咱们就不评价了,并且人家文档的东西始终是最新的,并且也有持续更新。复制 - 粘贴 - 发布的这类沙雕网友拜托大家不要浪费你们的时间了。净化学习环境从我作起!
固然一篇总结老是避免不了会用到原文档的一些例子。