前言
上篇文章展示了Vuex的基本使用方式,主要用了state和mutation这两种能力。本文会把Vuex这些能力都过一遍,尽量展示这些能力如何使用。注意,这里演示的代码都是基于上一篇文章《Vue学习(四)——Vuex入门》进行修改。所以请读者在操作代码时先按上篇文章搭起项目。
State
state
上一篇文章我们已经展示过state怎么用,通常会在计算属性中获取,因为这样就能在其变化时重新求值。这里就不展示state了。
state还有一个概念是mapState。当存在多个state的属性需要被组件使用时,可能如下,当使用的属性比较多时看起来会比较臃肿:
export default {
name: 'MyMessage',
data(){
return {
}
},
computed:{
message1(){
return this.$store.state.count1
},
message2(){
return this.$store.state.count2
},
message3(){
return this.$store.state.count3
}
}
}
mapState
我们也有简洁一点的方式,就是通过mapState,如下:
import { mapState } from 'vuex' //不要忘了引入mapState
export default {
name: 'MyMessage',
data(){
return {
}
},
computed: mapState({
message1: state => state.count1, //箭头函数,简洁的写法
message2: 'count2', //可以通过字符串获取同名变量,这种方式最简洁
message3: function(){ //如果需要函数计算则写个function
return this.$store.state.count3
}
})
}
记住,使用mapState不要忘了第一行的import引入。看起来使用mapState之后,是相对简洁了一些。
...mapState
上面使用mapState还可能产生一个小问题,就是它霸占了整个computed,如果还有计算属性混合使用呢?这里要用到...mapState,利用对象展开运算符,例如:
import { mapState } from 'vuex'
export default {
name: 'MyMessage',
data(){
return {
}
},
computed:{
ten(){
return this.$store.state.count + 10;
},
...mapState({
message: 'count'
})
}
}
Getter
有时候我们可能会对state取出来的值进行一些处理再展示,例如上面例子的ten(),对state.count加10再返回。如果好些组件都需要用到这个加10的方法,我们是否需要在每个用到的组件都写一遍这个加10的算法呢?如果遇到更复杂的计算方法,是否每个组件都要复制粘贴一份复杂计算的方法呢?
getters
getter的作用就是相当于Vuex中的computed,对于那些多个组件都需要使用的计算方法,可以写在getter里面。我们修改一下上一篇文章src/store/index.js文件,在getter中加上ten()这个方法:
import Vue from 'vue'
import Vuex from 'vuex'
//挂载Vuex
Vue.use(Vuex)
//创建VueX对象
const store = new Vuex.Store({
state: {
count: 0
},
getters: {
ten: state => {
return state.count + 10
}
},
mutations: {
increment (state) {
state.count++
}
}
})
export default store
另外我们修改一下src/components/myMessage.vue,把getters里面的ten给用上去看看效果:
<template>
<div>
<p>myMessage的值: {{ message }}</p>
<p>ten的值: {{ ten }}</p>
</div>
</template>
<script>
export default {
name: 'MyMessage',
data(){
return {
}
},
computed:{
ten(){
return this.$store.getters.ten
},
message(){
return this.$store.state.count
}
}
}
</script>
运行程序,点击按钮4次,看看是否实现了效果:
这个例子看起来getters好像可有可无,但如果处理的函数不是一行而是四五行,被用到的组件不止myMessage一个,而是三四个时呢?或许这时候就能体现getters的价值。
上面myMessage访问getters是通过属性访问,另外还可以通过方法访问。意思是可以让getters返回一个函数,组件调用getters的函数,这样还可以让组件传参给getters。我们改改src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
//挂载Vuex
Vue.use(Vuex)
//创建VueX对象
const store = new Vuex.Store({
state: {
count: 0
},
getters: {
ten: state => {
return state.count + 10
},
plusNum: state => (num) => {
return state.count + num
}
},
mutations: {
increment (state) {
state.count++
}
}
})
export default store
上面在getters中添加plusNum,这个函数允许调用者传入一个num参数,返回state.count+num的值。
接下来我们改改src/components/myMessage.vue,把plusNum的效果体现上去:
<template>
<div>
<p>myMessage的值: {{ message }}</p>
<p>ten的值 {{ ten }}</p>
<p>five的值 {{ five }}</p>
</div>
</template>
<script>
export default {
name: 'MyMessage',
data(){
return {
}
},
computed:{
ten(){
return this.$store.getters.ten
},
five(){
return this.$store.getters.plusNum(5)
},
message(){
return this.$store.state.count
}
}
}
</script>
这里我们在计算属性中添加一个方法five,调用plusNum并传入参数5。然后运行项目
当点击按钮是,可以看到3个值都加一。
mapGetters
mapGetters的使用与mapState基本类似,这里就直接用官网的例子:
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// 使用对象展开运算符将 getter 混入 computed 对象中
...mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
])
}
}
如果你想将一个 getter 属性另取一个名字,使用对象形式:
...mapGetters({
// 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`
doneCount: 'doneTodosCount'
})
Mutation
前一篇文章也讲过,更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。mutation和Vue中的事件处理方式很类似,都是通过发送消息+回调函数的形式进行处理。前面的例子,相信读者已经大致了解。调用方通过store.commit发送请求,然后mutations中对应的方法进行回调。Mutation的使用这里就不展示了,上一篇文章已经展示过。
使用Mutation要注意,mutations中的函数最好使用同步函数,不要异步函数。这是官网上说的。后来我专门试了一个例子,例如我们把src/store/index.js修改为:
import Vue from 'vue'
import Vuex from 'vuex'
//挂载Vuex
Vue.use(Vuex)
//创建VueX对象
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
setTimeout(()=>{
state.count++
}, 1000)
}
}
})
export default store
上面我们把increment修改为异步执行,这样当我们点击按钮触发时,过1秒state.count才会加1。运行项目后,执行的结果并没有什么问题,所以mutation异步执行函数也并不是不行。其实真正的问题在于devTools,即f12后的vue调试工具那里,如下图:
当我们点击了5次后,即使异步执行完,devTools工具中的值依然不会改变。devTools没办法跟踪mutation异步状态变化,这个不利于调试,所以官网不建议在mutation中使用异步函数。如果要使用,则在Action中使用。下面我们介绍Action如何使用。
Action
在我看来,因为mutation不建议使用异步函数,所以就把异步执行移到了action这里。action本身不做什么事情,action的功能就是负责调用mutation的。
我们修改一下src/store/index.js,加入action,并且尝试异步执行:
import Vue from 'vue'
import Vuex from 'vuex'
//挂载Vuex
Vue.use(Vuex)
//创建VueX对象
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
},
},
actions: {
increment (context){
setTimeout( ()=>{
context.commit('increment')
}, 1000)
}
}
})
export default store
然后修改src/components/myButton.vue,把原来通过commit触发mutation的方式改为dispatch触发action:
<template>
<div>
<button v-on:click="countFun">clicked me</button>
</div>
</template>
<script>
export default {
name: "MyButton",
methods: {
countFun() {
this.increment();
},
increment() {
this.$store.dispatch('increment')
}
},
};
</script>
通过action异步调用,就可以避免异步执行mutation时devtools遇到的无法跟踪状态的问题。
Module
modules
当store对象的内容比较多的时候,可能会显得比较臃肿。我们可以通过modules对store进行模块化,修改src/store/index.js:
import Vue from 'vue'
import Vuex from 'vuex'
//挂载Vuex
Vue.use(Vuex)
const moduleA = {
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
},
}
}
const moduleB = {
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
},
}
}
//创建VueX对象
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
export default store
上面代码创建moduleA和moduleB两个模块,然后通过mutations属性挂到store对象下。
接着修改src/components/myButton.vue:
<template>
<div>
<button v-on:click="incrementA">click A</button>
<button v-on:click="incrementB">click B</button>
</div>
</template>
<script>
export default {
name: "MyButton",
methods: {
incrementA() {
this.$store.commit('increment')
},
incrementB() {
this.$store.commit('increment')
}
},
};
</script>
这里我们有两个按钮,点击后都是触发increment。
接下来修改src/components/myMessage.vue:
<template>
<div>
<p>myMessageA的值: {{ messageA }}</p>
<p>myMessageB的值: {{ messageB }}</p>
</div>
</template>
<script>
export default {
name: 'MyMessage',
computed:{
messageA(){
return this.$store.state.a.count
},
messageB(){
return this.$store.state.b.count
}
}
}
</script>
我们通过“this.$store.state.模块名”的方式访问变量。
然后运行项目,并尝试点击两个按钮
可以看到,当我们点击任意一个按钮,会使两个值都加一。意思是a模块和b模块的increment都被触发了。那我们如何控制只触发其中一个呢,不同模块下的mutation方法如果存在同名怎么解决?
命名空间
解答上面的疑问,就是使用命名空间。每个模块的命名空间属性namespaced都默认为false,这时mutation、action、getter都默认注册到全局命名空间。如果我们设置为true,其commit方法的调用路径就会加上模块名。我们尝试修改src/store/index.js:
import Vue from 'vue'
import Vuex from 'vuex'
//挂载Vuex
Vue.use(Vuex)
const moduleA = {
namespaced: true,
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
},
}
}
const moduleB = {
namespaced: true,
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
},
}
}
//创建VueX对象
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
export default store
a和b模块都设置namespaced属性为true。
由于设置了命名空间,因此commit的调用路径就要修改了,我们修改src/components/myButton.vue:
<template>
<div>
<button v-on:click="incrementA">click A</button>
<button v-on:click="incrementB">click B</button>
</div>
</template>
<script>
export default {
name: "MyButton",
methods: {
incrementA() {
this.$store.commit('a/increment')
},
incrementB() {
this.$store.commit('b/increment')
}
},
};
</script>
这里把两个commit触发的方法都加上了模块名。重新启动项目后,分别点击两个按钮数次,可以发现按钮各自控制一个值的改变。
关于命名空间的使用还有不少内容。建议去官网学习,本文只是展示一些简单使用方式。
小结
本文展示了Vuex的五个核心概念的基本使用方式。内容较多,不过也就是几个文件改来改去而已:)。Vuex本身内容不多,其他暂时不打算讲~~~~~~