模块化的使用
# 前言
上一篇文章我们简单介绍了一下 Vuex 的简单使用,但随之也会产生一个问题。由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。尤其是在多人开发中会显得特别不便利。
# 介绍
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割。深入学习可能还是要去看官方文档 (opens new window)
语法:
在Vuex的配置中通过modules配置不同的模块,modules中模块的配置语法是模块名:{state,getters,mutations,action,modules}
import Vue from 'vue'
import Vuex from 'vuex'
import commend from './commend'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
name: 'root'
},
mutations: {
},
actions: {
},
modules: {
commend: {
state: {
name: '小明',
age: 18
},
getters: {
//some getters
},
mutation: {
// some mutations
},
actions: {
// some action
},
modules: {
// some modules
}
}
}
})
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
虽然在我们在实例化vuex.Store时创建了不同的模块,但是最终生成的store对象所有数据(包含子模块的数据)依然存储在同一个state getters mutations actions中。
# 命名空间
vuex中的modules支持命名空间,设置了命名空间的模块与没有设置命名空间的模块存在一定的区别。
语法:在需要开启命名空间的module模块中添加一个配置选项,namespaced:true
# module state
语法:存放在module中的state,是否开启命名空间使用方式都是一样的。都是通过store.state.
模块名.state属性名
let store = new Vuex.Store({
state: {
name: 'root'
},
modules: {
commend: {
state: {
name: '小王'
}
}
a1: {
namespaced: true, // 开启命名空间
state: {
name: '老李头',
age: 56
}
}
}
})
// store.state.name => "root"
// store.state.commend.name => "小王"
// store.state.a1.name => "老李头"
// store.state.a1.age => 56
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
实例:辅助函数mapState获取指定模块的属性,必须使用 "属性名:函数的形式"
<template>
<div id="app">
<button @click="showStore">show</button>
<p>{{name}}</p>
<p>{{commendName}}</p>
<p>{{a1Name}}</p>
</div>
</template>
<script>
import {mapState} from 'vuex'
export default {
name: 'App',
computed: {
...mapState(['name']), // $store.state.name => 'root'
...mapState({
commendName: state => state.commend.name, // $store.state.commend.name => '小王'
a1Name: ({a1}) => a1.name // $store.state.a1.name => '老李头'
})
},
methods: {
showStore() {
console.log(this.$store)
}
}
}
</script>
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
# module getters
语法:所有module中的getter是直接存储在store.getters中的,依然通过store.getters.属性名访问
。
所以在开发中如果你的模块如果没有开启命名空间,一定要保证未命名模块与未命名模块之间/未命名模块与根getters之间不能有同名getters属性。
export default new Vuex.Store({
state: {
name: 'root'
},
getters: {
// store.getters.test
test(state) {
return state.name + '!!'
}
//ERROR! 注意子模块a1中也有一个同名getter,因为a1没有开启命名空间,所以导致两个getter命名冲突产生报错
reverseName(state) {
return state.name.split('').reverse().join('')
}
},
modules: {
a1: {
state: {
name: '老李头',
age: 56
},
getters: {
// store.getters.reverseName
reverseName(state) {
return state.name.split('').reverse().join('')
}
}
}
}
})
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
注意:当开启了命名空间后module,getters 获取方式发生改变
变为 -->
store.getters['模块名/getter属性名']
所以开启命名空间的getter可以与其他模块根store中的getter同名
export default new Vuex.Store({
state: {
name: 'root'
},
getters: {
// store.getters.test
test(state) {
return state.name + '!!'
}
//正确, a1模块中虽然getters内包好一个与当前根getter同名的属性,但是因为a1模块开启了命名空间.
// a1的getter最终生成后属性名会发生改变就不会造成命名冲突
reverseName(state) {
return state.name.split('').reverse().join('')
}
},
modules: {
a1: {
namespaced: true,
state: {
name: '老李头',
age: 56
},
getters: {
// store.getters.reverseName
reverseName(state) {
return state.name.split('').reverse().join('')
}
}
}
}
})
// store.getters.reverseName 根store的getter => 'toor'
// store.getters['a1/reverseName'] a1模块的getter => "头李老"
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
实例:辅助函数mapGetter 获取module的getter方法没有变化的
import { mapGetters} from 'vuex'
export default {
name: 'App',
computed: {
...mapGetters(['reverseName']),
...mapGetters({a1ReverseName: 'a1/reverseName'})
},
methods: {
showStore() {
console.log(this.$store)
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
注意:模块中getters函数包含四个参数
参数一 state -------- 当前模块的state
参数二 getters -------- 当前模块的getters(如果当前模块没有开启命名空间 该参数的值等于参数四)
参数三 rootState -------- 根state store的state
参数四 rootGetter -------- 根getters store的getters
// 开启命名空间模块getters函数的参数
export default new Vuex.Store({
state: {
name: 'root'
},
getters: {
reverseName(state) {
return state.name + '!!'
}
},
modules: {
a1: {
namespaced: true, // 开启命名空间
state: {
name: '老李头',
age: 56
},
getters: {
reverseName(state,getters, rootState, rootGetters) {
console.log('state',state) // {name:"老李头", age: 56}
console.log('getters',getters) // {reverseName: "头李老"}
console.log('rootState',rootState) // {name: "root", a1: {name:"老李头", age: 56}}}
console.log('rootGetters',rootGetters) // {reverseName: "root!!", 'a1/reverseName':"头李老"}
return state.name.split('').reverse().join('')
}
}
}
}
})
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
// 未开启命名空间模块getters函数的参数
export default new Vuex.Store({
state: {
name: 'root'
},
getters: {
rootReverseName(state) {
return state.name + '!!'
}
},
mutations: {
},
actions: {
},
modules: {
a1: {
state: {
name: '老李头',
age: 56
},
getters: {
reverseName(state,getters, rootState, rootGetters) {
console.log('state',state) // {name:"老李头", age: 56}
console.log('getters',getters) // {rootReverseName: "root!!", 'reverseName':"头李老"}
console.log('rootState',rootState) // {name: "root", a1: {name:"老李头", age: 56}}}
console.log('rootGetters',rootGetters) // {rootReverseName: "root!!", 'reverseName':"头李老"}
return state.name.split('').reverse().join('')
}
}
}
}
})
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
# module mutation 和 action
语法:所有mutation和action也是直接存储在store中的,依然通过store.dispatch('mutation名') store.dispatch('action名')
触发的 。
在开发中如果你的模块如果没有开启命名空间,mutation与action 在模块与模块之间 或模块与根store之间存在同名mutation / action 是不会造成命名冲突不会报错的,但是如果commit / dispatch 这些同名 mutation / action时他们将都会执行。
export default new Vuex.Store({
state: {
name: 'root'
},
getters: {
rootReverseName(state) {
return state.name + '!!'
}
},
mutations: {
myMutation() {
console.log('根mutation的 myMutation方法被触发了')
}
},
actions: {
myAction() {
console.log('根action的 myAction方法被触发了')
}
},
modules: {
a1: {
mutations: {
myMutation() {
console.log('模块 a1 mutation 的 myMutation方法被触发了')
}
},
actions: {
myAction() {
console.log('模块 a1 action的 myAction方法被触发了')
}
}
}
}
})
// 触发 $store.commit('myMutation') 时 => '根mutation的 myMutation方法被触发了'
// '模块 a1 mutation 的 myMutation方法被触发了'
// 触发 $store.dispatch('myAction') 时 => '根action的 myAction方法被触发了'
// '模块 a1 action的 myAction方法被触发了'
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
注意:当开启了命名空间后module,mutation与action方法名会发生改变
变为 -->
'模块名/mutation名' '模块名/action名'
export default new Vuex.Store({
state: {
name: 'root'
},
getters: {
rootReverseName(state) {
return state.name + '!!'
}
},
mutations: {
myMutation() {
console.log('根mutation的 myMutation方法被触发了')
}
},
actions: {
myAction() {
console.log('根action的 myAction方法被触发了')
}
},
modules: {
a1: {
namespaced: true, // 开启命名空间
state: {
name: '老李头',
age: 56
},
mutations: {
myMutation() {
console.log('模块 a1 mutation 的 myMutation方法被触发了')
}
},
actions: {
myAction() {
console.log('模块 a1 action的 myAction方法被触发了')
}
}
}
}
})
// $store.commit('myMutation') => '根mutation的 myMutation方法被触发了'
// $store.commit('a1/myMutation') => '模块 a1 mutation 的 myMutation方法被触发了'
// $store.dispatch('myAction') => '根action的 myAction方法被触发了'
// $store.dispatch('a1/myAction') => '模块 a1 action的 myAction方法被触发了'
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
注意: 模块中mutation函数无论是否开启具名空间依然只包含两个参数
参数一: 当前模块的局部state
参数二: 载荷
export default new Vuex.Store({
state: {
name: 'root'
},
getters: {
rootReverseName(state) {
return state.name + '!!'
}
},
mutations: {
myMutation() {
console.log('根mutation的 myMutation方法被触发了')
}
},
modules: {
a1: {
namespaced: true, // 开启命名空间
state: {
name: '老李头',
age: 56
},
mutations: {
myMutation(state, payload) { // state => { name: '老李头',age: 56}
console.log('模块 a1 mutation 的 myMutation方法被触发了')
}
}
}
}
})
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
注意: 模块中action函数无论是否开启具名空间依然只包含两个参数
参数一: context 与root state比较多了两个属性
参数二: 载荷
commit // 用来提交mutation的方法
dispatch // 用来分发action的方法
getters // 命名空间开启局部getters 没有开启命名空间 全局getters
rootGetters // 全局getter
rootState // 全局state
state // 当前模块的statestate
2
3
4
5
6
注意:
命名空间开启的模块
action内部context
.commit 提交mutation时dispatch分发action时会自动添加命名空间前缀
,从而实现只提交当前模块mutation/action
的效果
export default new Vuex.Store({
state: {
name: 'root'
},
getters: {
rootReverseName(state) {
return state.name + '!!'
}
},
mutations: {
myMutation() {
console.log('根mutation的 myMutation方法被触发了')
}
},
actions: {
myAction() {
console.log('根action的 myAction方法被触发了')
}
},
modules: {
a1: {
namespaced: true, // 开启命名空间
state: {
name: '老李头',
age: 56
},
mutations: {
myMutation() {
console.log(arguments,'模块 a1 mutation 的 myMutation方法被触发了')
}
},
actions: {
myAction(context) {
context.commit('myMutation') // 会隐式转化为你context.commit('a1/myMutation')
context.dispatch('a1/myAction') // 会隐式转化为你context.dispatch('a1/a1/myAction')
console.log(arguments,'模块 a1 action的 myAction方法被触发了')
}
}
}
}
})
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
注意:
如果你想要在开启命名空间模块的
action中使用
context提交/分发全局mutation/action请在
commit dispatch方法传入
第三个参数{root:true}代表调用全局
dispatch('someOtherAction', null, { root: true })
commit('someMutation', null, { root: true })
2
3
例子如下
export default new Vuex.Store({
state: {
name: 'root'
},
getters: {
rootReverseName(state) {
return state.name + '!!'
}
},
mutations: {
myMutation() {
console.log('根mutation的 myMutation方法被触发了')
}
},
actions: {
myAction() {
console.log('根action的 myAction方法被触发了')
}
},
modules: {
a1: {
namespaced: true, // 开启命名空间
state: {
name: '老李头',
age: 56
},
mutations: {
myMutation() {
console.log(arguments,'模块 a1 mutation 的 myMutation方法被触发了')
}
},
actions: {
myAction(context) {
context.commit('myMutation',null,{ root: true }) // 提交根mutation 'myMutation'
context.dispatch('myAction',null,{ root: true }) // 分发根action 'myAction'
console.log(arguments,'模块 a1 action的 myAction方法被触发了')
}
}
}
}
})
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
# CodeSandbox
CodeSandbox在线代码演示 (opens new window)
注意 如遇到CodeSandBox打开失败,请尝试按下图操作,然后分窗口就能一边看代码一边看效果啦