Appearance
Vuex
Modules
1. Modules 使用时机
当项目足够庞大,值得分离模块时,就需要使用 Modules。
2. 不用与启用命名空间的使用方式对比
假设一个 module 叫mod
,它的namespaced
可以为true
,也可以为false
:
js
export default {
namespaced: true,
state: {
sta: 1,
},
mutations: {
mut(state) {
state.sta += 1;
},
},
actions: {
act({ commit }) {
commit('mut');
},
},
getters: {
getsta(state) {
return state.sta;
},
},
};
不用与启用命名空间的使用方式对比如下:
TIP
下文中的“不用”即不启用命名空间,“启用”即启用命名空间。
读取 state
- 不用:
this.$store.state.mod.sta
- 启用:同上
- 不用:
读取 getter
- 不用:
this.$store.getters.getsta
- 启用:
this.$store.getters['mod/getsta']
- 不用:
调用 commit
- 不用:
this.$store.commit('mut');
- 启用:
this.$store.commit('mod/mut')
- 不用:
调用 dispatch
- 不用:
this.$store.dispatch('act');
- 启用:
this.$store.dispatch('mod/act')
- 不用:
mapState
- 不用:
...mapState({ sta: state => state.mod.sta })
- 启用:
...mapState('mod', ['sta'])
或...mapState('mod', { sta: 'sta' })
或...mapState('mod', { sta: state => state.sta })
- 不用:
mapGetters
- 不用:
...mapGetters(['getsta'])
或...mapGetters({ getsta: 'getsta' })
- 启用:
...mapGetters('mod', ['getsta'])
或...mapGetters('mod', { getsta: 'getsta' })
- 不用:
mapMutations
- 不用:
...mapMutations(['mut'])
或...mapMutations({ mut: 'mut' })
- 启用:
...mapMutations('mod', ['mut'])
或...mapMutations('mod', { mut: 'mut' })
- 不用:
mapActions
- 不用:
...mapActions(['act'])
或...mapActions({ act: 'act' })
- 启用:
...mapActions('mod', ['act'])
或...mapActions('mod', { act: 'act' })
- 不用:
对比结果:
不用命名空间:
- 优点:代码简单
- 缺点:必须注意避免重名
启用命名空间:
- 优点:模块清晰
- 缺点:代码量多,要记忆和书写模块名
结论:
全局的 State 、Getters、Mutations、Actions 写在 store.js 里。
非全局的 State 、Getters、Mutations、Actions 要写在模块里,且一定要启用命名空间,此种模块叫局部模块,这样优势是完全不必担心重名问题,也不用思考某个模块是否启用了命名空间,因为一定是启用了命名空间。
State
必须被 3 个以上组件使用,而且用常见的数据共享方式不够优雅时,才应考虑写入 Vuex。
Getters
❌ 错误实践:
一些集成框架在全局模块使用 Getters 来引用局部模块的 State,它的想法是可以将this.$store.state.mod.sta
缩短成this.$store.getters.sta
,可以少写几个字符。但是,这是错误的实践。
❓ 错误分析:
因为该框架从根本上没有正确利用全局模块和局部模块的各自优势,this.$store.state.mod.sta
明显更清晰,既然故意使用了命名空间,为什么又要抹掉命名空间?而且有框架最神奇的操作是使用name: (state) => state.mod.name
,这种做法有“三蠢”,第一蠢是使用name
这个简单的通用词,第二蠢是不顾name
重名风险,第三蠢是明明启用命名空间还尚且能区分,结果它偏要反其道而行之,故意抹掉命名空间,最后的希望也破灭了。
✔️ 正确实践:
在需要充当计算属性时,真正有计算过程代码时,才应使用 Getters。
虽然没有计算属性的意义,但是为了从一个对象中提取一个著名的属性,比如
userName
存在于state.userInfo.userName
,又是常用属性,就值得添加一个 getters。不要在 store.js 的 getters 中引用 Modules 里的 state,这会让特意构成的命名空间消失掉。
Mutations
必须只写与 State 相关的同步方法,且必须是同步方法。
遵守 Vuex 官方要求,方法名使用全大写蛇形结构。
修改 State:
- 修改本模块的 State:直接操作,比如
state.sta += 1
。 - 修改其他模块的 State:这需要在 mutation 里调用其他模块的 Mutations,具体见下文。
- 修改根的(或不用命名空间的模块的)State:这需要在 mutation 里调用根的 Mutations,具体见下文。
- 调用其他 Mutations(代码里的
this
指向 Vuex 实例):
- 调用本模块的 Mutations:在自身 mutation 里写入
this.commit('<本模块名>/<其他 mutation>');
。 - 调用其他模块的 Mutations:在自身 mutation 里写入
this.commit('<其他模块名>/<其他 mutation>');
。 - 调用根的(或不用命名空间的模块的)Mutations:在自身 mutation 里写入
this.commit('<根的 mutation>');
。
Actions
必须只写与 State 相关的异步方法,且必须是异步方法。
遵守 Vuex 官方要求,方法名使用全大写蛇形结构。
调用 Mutations(代码里的
this
指向 Vuex 实例):
- 调用本模块的 Mutations:属于最常用写法,如
commit('<其他 mutation>');
。 - 调用其他模块的 Mutations:在自身 action 里写入 👍
this.commit('<其他模块名>/<其他 mutation>');
或 👎commit('<其他模块名>/<其他 mutation>', null, { root: true })
。 - 调用根的(或不用命名空间的模块的)Mutations:在自身 action 里写入 👍
this.commit('<根的 mutation>');
或 👎commit('<根的 mutation>', null, { root: true })
。
- 调用其他 Actions(代码里的
this
指向 Vuex 实例):
- 调用本模块的 Actions:常用写法,如
dispatch('<其他 action>');
。 - 调用其他模块的 Actions:在自身 action 里写入 👍
this.dispatch('<其他模块名>/<其他 action>');
或 👎dispatch('<其他模块名>/<其他 action>', null, { root: true })
。 - 调用根的(或不用命名空间的模块的)Actions:在自身 action 里写入 👍
this.dispatch('<根的 action>');
或 👎dispatch('<根的 action>', null, { root: true })
。
跨模块调用
尽量避免跨模块调用。
使用
this
比使用{ root: true }
更加简洁,同时不产生歧义,应推荐使用this
。
是否应使用 Computed
- 如果某个 State 、Getters、Mutations、Actions 在组件中没有复用 3 次以上,最好不要使用计算属性,因为:
写计算属性需要代码横跳,从使用的位置横跳到
computed
,再回跳回来;变量名的语义不清晰,往往第一时间看不出来它来自于 Vuex 还是来自于
data
,比如this.sta
可能来自于 Vuex 也可能来自于data
,但是this.$store.state.mod.sta
毫无疑问来自于 Vuex。
- 反之,如果某个 State 、Getters、Mutations、Actions 在组件中复用 3 次以上,可以考虑使用计算属性,但也不是绝对,毕竟
$store.state.xxx
全是优点,没有缺点。