背景
由于上一次写的文章不够直观,因此决定重写一下Vuex的基本使用。
Vuex的前置知识
什么是Vuex?
Vuex是一个状态管理工具,采用集中式存储管理应用的所有组件的状态,并且以相应的规则保证状态可预测的方式发生改变。本质就是把组件的共享状态和触发修改状态的函数抽离出来,这样就能获得状态或者触发行为。
什么情形下适合使用Vuex?
如果不打算开发大型单页应用,使用Vuex就会变得很冗余,就会有“牛刀杀鸡”的感觉,因为它会附带很多概念。这种情况下使用store模式就足够了。
如果正在开发一个中大型的单页应用,Vuex是更好的选择。
安装
直接下载
链接地址:unpkg.com/vuex@3.6.2/…,这个链接会一直指向在npm发布的最新版本。同样我们也可以指向其他的版本,比如unpkg.com/vuex@2.0.0,在项目中,通过script标签指向引用的文件。
npm
npm install vuex --save
yarn
yarn add vuex
经历以上的安装过程之后,需要在全局引入vuex,然后像router一样,使用Vue注册。
import Vuex from "vuex"
Vue.use(Vuex)
每一个Vuex的核心就是store,即仓库,可以说成是一个容器,它包含着大部分的共享状态。
Vuex的特点
- Vuex的状态是响应式的,当Vue组件从store里面读取状态时。如果store中的状态发生变化,那么相应的消费组件也会高效的更新状态。
- 开发者不能直接更改store中的状态,改变store中状态的唯一途径就是显示地提交mutation,这就使开发者可以很方便地跟踪每一个状态的变化。
Vuex的初体验
入口文件代码演示
下面是一个在入口文件中,从引入到使用的代码演示:
// index.js
import Vue from 'vue'
import App from './App.vue'
// 1.引用
import Vuex from "vuex"
Vue.config.productionTip = false
// 2.全局注册
Vue.use(Vuex)
// 3.创建一个仓库
const store=new Vuex.Store({
state:{
count:0
},
mutations:{
add(state){
state.count++
}
}
})
// 4.触发mutation
store.commit("add")
console.log(store.state.count); //1
new Vue({
// 5.注入到Vue对象
store,
render: h => h(App),
}).$mount('#app')
总结一下上面的步骤:
- 引入vuex;
- 全局注册Vuex,
Vue.use(Vuex); - 创建一个Vuex仓库,仓库里面可以包含五个选项:state(状态)、getters(对状态的过滤)、mutations(状态修改)、actions(异步提交修改状态信息)、modules(模块);
- 触发mutation:通过commit触发仓库中的状态更新,
store.commit("对应的mutation"); - 将仓库注入到Vue对象中,也可以挂载到Vue对象原型上;
组件中获取状态、修改状态的代码演示
<template>
<div class="child">
我是Child组件
{{count}}
<button @click="add">+1</button>
<Child2/>
</div>
</template>
<script>
import Child2 from '@/components/Child2'
export default {
components:{Child2},
// 1.获取state
computed:{
count(){
return this.$store.state.count
}
},
methods:{
// 2.触发commit
add(){
this.$store.commit("add")
}
}
}
</script>
效果图: 小结:
- 在组件中,使用
this.$store获取仓库对象; - 获取通过computed方法获取,因为是需要不断地计算;
Vuex的核心概念
state
mapState辅助函数
这种模式是由于Vuex通过store选项,将Vuex注入到每一个组件中。但是,一个组件每次使用一个mutation,就要生成一个计算属性,这样就会使代码变得冗余。为了解决这个问题,我们可以使用mapState辅助函数帮助我们生成计算属性。
<script>
// 第一步,引入mapState模块
import {mapState} from "vuex"
export default{
// 第二步,获取状态
// 方法一
computed:mapState({
count:state=>state.count
}),
// 方法二
computed:mapState([
"count"
]),
}
</script>
小结:在组件中获取状态的三种方式
方式一,使用笨方法:this.$store.state.相关的状态
<script>
export default{
computed:{
count(){
return this.$store.state.count
}
}
}
</script>
方式二,引入mapState模块,接收对象
<template>
<div class="child">
我是Child组件
{{count}}
<Child2/>
</div>
</template>
<script>
import Child2 from '@/components/Child2'
import {mapState} from "vuex"
export default {
components:{Child2},
computed:mapState({
// 计算属性名:参数代表仓库的state
count:state=>state.count
})
}
</script>
效果图:
方式二小结:
- 需要从vuex模块引入mapState;
- mapState的键名代表计算属性的名称;
- mapState的键值是一个回调函数,回调函数的形参代表仓库的state;
方式三,引入mapState模块,接收数组参数
<template>
<div class="child">
我是Child组件
{{count}}
<Child2/>
</div>
</template>
<script>
import Child2 from '@/components/Child2'
import {mapState} from "vuex"
export default {
components:{Child2},
computed:mapState([
// 映射 this.count 为 store.state.count
'count'
])
}
</script>
效果图:
小结方式三:
- 需要从vuex模块引入mapState;
- 如果需要引入数组的形式,比如
['xxx'],就相当于将computed属性xxx,映射为仓库的xxx状态,即this.xxx => store.state.xxx,语法上不对,只是为了表达意思。 - 这种写法,引号里面是需要的state,然后使用state名称作为获取状态的变量;
对象展开操作符
上面总结了三种在computed属性里面获取状态的方式,后面几乎不会使用第一种方式,因为这种方式太过麻烦,而且“又老又土”。那么,问题来了,在computed属性里面写的都是mapState对象,怎么样才能将mapState对象和组件本身的计算属性混合在一起呢?
解决问题的办法就是,使用对象的展开操作符。
<template>
<div class="child">
我是Child组件
<br/>
{{count}}
<br/>
{{localComputed}}
<Child2/>
</div>
</template>
<script>
import Child2 from '@/components/Child2'
import {mapState} from "vuex"
export default {
components:{Child2},
computed:{
localComputed(){
return "我是局部的计算属性"
},
...mapState([
'count'
])
}
}
</script>
在computed属性里面写...mapState({})或者...mapState([]),然后在里面正常获取store中的状态。
Getters
getters选项是store的计算属性,getters选项里面的函数,接受一个参数,这个参数就代表仓库的状态state。那么如何访问到经过处理后的getters属性值呢?
代码演示,需求:状态中的数组arr,只要done为true的项目
在main.js中设置getters:
import Vue from 'vue'
import App from './App.vue'
// 1.引用
import Vuex from "vuex"
Vue.config.productionTip = false
// 2.全局注册
Vue.use(Vuex)
// 3.创建一个仓库
const store=new Vuex.Store({
state:{
count:0,
arr:[
{name:"鲁班",done:true},
{name:"百里守约",done:false},
{name:"娜可露露",done:true}
]
},
getters:{
filterArr(state){
return state.arr.filter(item=>item.done)
}
}
})
new Vue({
// 5.注入到Vue对象
store,
render: h => h(App),
}).$mount('#app')
在组件中获取getters选项:
<template>
<div class="child">
我是Child组件
<br/>
{{count}}
<br/>
<Child2/>
</div>
</template>
<script>
import Child2 from '@/components/Child2'
export default {
components:{Child2},
computed:{
count(){
return this.$store.getters.filterArr
}
}
}
</script>
效果图:
上面提到过,getters选项是计算属性,它的返回结果是否有缓存,要分情况讨论。它如果通过state直接操作,并且返回处理后的state,这时候它没有缓存。如果基于其他的getter函数,返回state,这时候它有缓存。总而言之,getter函数依赖于state,就没有缓存;getter函数依赖于其他getter函数,就有缓存。
有缓存的情况
原理:一个getter函数接受两个参数,第一个参数代表仓库的所有状态,即state。第二个形参代表仓库中其他的所有getter函数。
代码演示,需求:数组过滤之后,返回一个新数组的长度。
在main.js中设置getters:
import Vue from 'vue'
import App from './App.vue'
// 1.引用
import Vuex from "vuex"
Vue.config.productionTip = false
// 2.全局注册
Vue.use(Vuex)
// 3.创建一个仓库
const store=new Vuex.Store({
state:{
count:0,
arr:[
{name:"鲁班",done:true},
{name:"百里守约",done:false},
{name:"娜可露露",done:true}
]
},
getters:{
// 获取done为true的数组
filterArr(state){
return state.arr.filter(item=>item.done)
},
// 获取新数组的长度
getNewLength(state,getters){
return getters.filterArr.length
}
}
})
new Vue({
// 5.注入到Vue对象
store,
render: h => h(App),
}).$mount('#app')
在组件中获取getters选项:
<template>
<div class="child">
我是Child组件
<br/>
{{count}}
<br/>
{{getNewLength}}
<Child2/>
</div>
</template>
<script>
import Child2 from '@/components/Child2'
export default {
components:{Child2},
computed:{
count(){
return this.$store.getters.filterArr
},
getNewLength(){
return this.$store.getters.getNewLength
}
}
}
</script>
效果图:
没有缓存的情况
需求:和上面的demo一样,数组过滤之后,返回一个新数组的长度。
在main.js中设置getters:
import Vue from 'vue'
import App from './App.vue'
// 1.引用
import Vuex from "vuex"
Vue.config.productionTip = false
// 2.全局注册
Vue.use(Vuex)
// 3.创建一个仓库
const store=new Vuex.Store({
state:{
count:0,
arr:[
{name:"鲁班",done:true},
{name:"百里守约",done:false},
{name:"娜可露露",done:true}
]
},
getters:{
// 获取新数组的长度
getNewLength(state){
return state.arr.filter(item=>item.done).length
}
}
})
new Vue({
// 5.注入到Vue对象
store,
render: h => h(App),
}).$mount('#app')
在组件中获取getters选项:
<template>
<div class="child">
我是Child组件
<br/>
{{getNewLength}}
<Child2/>
</div>
</template>
<script>
import Child2 from '@/components/Child2'
export default {
components:{Child2},
computed:{
getNewLength(){
return this.$store.getters.getNewLength
}
}
}
</script>
效果图:
mapGetters辅助函数
如果在组件中获取getter属性值,使用mapGetter辅助函数更方便,和mapState函数类似。 具体代码演示放到后面。
小结:在组件中获取getter的三种方式
首先,main.js中的代码是这样的,主要关注store相关的代码:
import Vue from 'vue'
import App from './App.vue'
// 1.引用
import Vuex from "vuex"
Vue.config.productionTip = false
// 2.全局注册
Vue.use(Vuex)
// 3.创建一个仓库
const store=new Vuex.Store({
state:{
count:0,
arr:[
{name:"鲁班",done:true},
{name:"百里守约",done:false},
{name:"娜可露露",done:true}
]
},
getters:{
// 获取新数组的长度
getNewLength(state){
return state.arr.filter(item=>item.done).length
}
}
})
new Vue({
// 5.注入到Vue对象
store,
render: h => h(App),
}).$mount('#app')
方式一:this.$store.getters.自定义属性
<template>
<div class="child">
我是Child组件
<br/>
{{getNewLength}}
<Child2/>
</div>
</template>
<script>
import Child2 from '@/components/Child2'
export default {
components:{Child2},
computed:{
getNewLength(){
// 获取getter属性
return this.$store.getters.getNewLength
}
}
}
</script>
方式二:引入mapGetters模块,接收数组参数
<template>
<div class="child">
我是Child组件
<br/>
{{getNewLength}}
<Child2/>
</div>
</template>
<script>
import Child2 from '@/components/Child2'
import {mapGetters} from "vuex"
export default {
components:{Child2},
computed:{
// 获取getter属性
...mapGetters([
"getNewLength"
])
}
}
</script>
和之前的mapState一样,计算属性名和getter属性名一样。
方式三:引入mapGetters模块,接收对象参数
<template>
<div class="child">
我是Child组件
<br/>
{{getLength666}}
<Child2/>
</div>
</template>
<script>
import Child2 from '@/components/Child2'
import {mapGetters} from "vuex"
export default {
components:{Child2},
computed:{
// 获取getter属性
...mapGetters({
"getLength666":"getNewLength"
})
}
}
</script>
一般情况下,个人觉得没必要用对象参数的形式,除非看getter属性名不爽,想自己搞一个。
和mapState不同,mapState的键值是一个回调函数,参数代表state。而mapGetter的键值是getter属性名,用引号括起来。
Mutation
mutations是vuex中的修改状态的方法,而且是唯一直接修改状态的方式,每一个mutation的属性值是一个回调函数,它的第一个参数代表,仓库的状态(state)。触发mutation的方法是通过调用store.commit("mutation属性名")。
Mutation可以在控制台产生快照。
mutation的代码演示
在main.js中设置mutations:
import Vue from 'vue'
import App from './App.vue'
// 1.引用
import Vuex from "vuex"
Vue.config.productionTip = false
// 2.全局注册
Vue.use(Vuex)
// 3.创建一个仓库
const store=new Vuex.Store({
state:{
count:0,
arr:[
{name:"鲁班",done:true},
{name:"百里守约",done:false},
{name:"娜可露露",done:true}
]
},
mutations:{
// 声明一个mutation
add(state){
state.count++
}
}
})
new Vue({
// 5.注入到Vue对象
store,
render: h => h(App),
}).$mount('#app')
在组件中提交mutation:
<template>
<div class="child">
我是Child组件
<button @click="add">+1</button>
<br/>
{{count}}
<Child2/>
</div>
</template>
<script>
import Child2 from '@/components/Child2'
import {mapState} from "vuex"
export default {
components:{Child2},
methods:{
add(){
this.$store.commit("add")
}
},
computed:{
...mapState(["count"])
}
}
</script>
效果图:
传参
在vuex中,函数传参也被称作提交载荷(Payload)。mutation函数可以接收很多个参数,第一个参数代表仓库的所有状态,即state。从第二个参数及后面的参数,都是我们手动加上去的形参,接收我们提交时传的数据,这些数据也被官方地称作载荷。
代码演示
需求:按钮被点击后,加一个数,这个数是自定义的。
在main.js中设置mutations:
import Vue from 'vue'
import App from './App.vue'
// 1.引用
import Vuex from "vuex"
Vue.config.productionTip = false
// 2.全局注册
Vue.use(Vuex)
// 3.创建一个仓库
const store=new Vuex.Store({
state:{
count:0,
arr:[
{name:"鲁班",done:true},
{name:"百里守约",done:false},
{name:"娜可露露",done:true}
]
},
mutations:{
// 声明一个mutation
add(state,n){
state.count+=n
}
}
})
new Vue({
// 5.注入到Vue对象
store,
render: h => h(App),
}).$mount('#app')
在组件中提交mutation:
<template>
<div class="child">
我是Child组件
<button @click="add">+1</button>
<br/>
{{count}}
<Child2/>
</div>
</template>
<script>
import Child2 from '@/components/Child2'
import {mapState} from "vuex"
export default {
components:{Child2},
methods:{
add(){
// 加10
this.$store.commit("add",10)
}
},
computed:{
...mapState(["count"])
}
}
</script>
效果图:
从代码运行原理上来看,mutation函数可以接收很多个参数,但是这样做,会导致代码整洁性不高,出错率增加。因此,实际开发中,只接受两个参数,第二个参数是一个对象,把想要传的参数都放在这个对象里面。
对象风格的传参方式
语法结构:
// 注册Mutation
mutations:{
事件名(state,obj){
// do something
}
}
// 传参
store.commit({
type:"事件名",
objProperty:"xxx"
})
解释:注册时,和之前的方式一样。传参时,有差别。commit仅仅接收一个对象,type属性值是需要触发的事件名,后面的都是obj下面的键值对,相当于{objProperty:"xxx"} => obj。
代码演示
需求:将上一页的需求再次通过对象传参的风格做一遍
在main.js中设置mutations:
import Vue from 'vue'
import App from './App.vue'
// 1.引用
import Vuex from "vuex"
Vue.config.productionTip = false
// 2.全局注册
Vue.use(Vuex)
// 3.创建一个仓库
const store=new Vuex.Store({
state:{
count:0,
arr:[
{name:"鲁班",done:true},
{name:"百里守约",done:false},
{name:"娜可露露",done:true}
]
},
mutations:{
// 声明一个mutation
add(state,obj){
state.count+=obj.n
}
}
})
new Vue({
// 5.注入到Vue对象
store,
render: h => h(App),
}).$mount('#app')
在组件中提交mutation:
<template>
<div class="child">
我是Child组件
<button @click="add">+1</button>
<br/>
{{count}}
<Child2/>
</div>
</template>
<script>
import Child2 from '@/components/Child2'
import {mapState} from "vuex"
export default {
components:{Child2},
methods:{
add(){
// 加10
this.$store.commit({
type:"add",
n:10
})
}
},
computed:{
...mapState(["count"])
}
}
</script>
实现了同样的效果
Vue的响应式规则
- 开发者最好在仓库的state中,初始化好所有需要的状态;
- 如果想在state中响应式添加一个对象,有两种情况:
- 追加一个新对象:
Vue.set(obj,"property",123) - 以新对象替换旧对象:
state.obj={...state.obj,newProperty:123}
- Mutation函数必须是同步的。每一条mutation都会被devtools记录,devtools需要捕捉前一状态、后一状态。如果mutation函数中如果有异步函数,devtools无法得知异步函数什么时候执行,也就无法记录状态的变更。
小结:在组件中提交Mutation的三种方式
和获取state、getter一样,提交Mutation也有辅助函数mapMutations。由于mapMutations的本质是触发事件,因此放在methods中。
首先,main.js中的代码是这样的,主要关注Mutation相关的代码:
import Vue from 'vue'
import App from './App.vue'
// 1.引用
import Vuex from "vuex"
Vue.config.productionTip = false
// 2.全局注册
Vue.use(Vuex)
// 3.创建一个仓库
const store=new Vuex.Store({
state:{
count:0
},
mutations:{
// 声明一个mutation
add(state,obj){
state.count+=obj.n
}
}
})
new Vue({
// 5.注入到Vue对象
store,
render: h => h(App),
}).$mount('#app')
方式一:笨方法,this.$store.commit({type:"事件名",……})
<template>
<div class="child">
我是Child组件
<button @click="add">+1</button>
<br/>
{{count}}
<Child2/>
</div>
</template>
<script>
import Child2 from '@/components/Child2'
import {mapState} from "vuex"
export default {
components:{Child2},
methods:{
add(){
// 提交Mutation
this.$store.commit({
type:"add",
n:10
})
}
},
computed:{
...mapState(["count"])
}
}
</script>
方式二,引入mapMutations模块,接收对象属性参数
<template>
<div class="child">
我是Child组件
<button @click="clickFn">+1</button>
<br/>
{{count}}
<Child2/>
</div>
</template>
<script>
import Child2 from '@/components/Child2'
import {mapState,mapMutations} from "vuex"
export default {
components:{Child2},
methods:{
...mapMutations({
// 将this.add666 映射为 this.$store.commit("add")
add666:"add"
}),
clickFn(){
this.add666({n:10})
}
},
computed:{
...mapState(["count"])
}
}
</script>
说明:
- mutation需要事件触发,比如点击事件;
- mapMutation的对象,键名是自定义的触发事件名,键值是之前在mutations选项中触发的事件名。
- 在触发事件中,使用
this.自定义触发事件名(实参),由于在mapMutation对象中,已经完成了映射关系,这里的this.自定义触发事件名(实参)===this.$store.commit("触发事件名",实参);
方式三,引入mapMutations模块,接收数组属性参数
<template>
<div class="child">
我是Child组件
<button @click="clickFn">+1</button>
<br/>
{{count}}
<Child2/>
</div>
</template>
<script>
import Child2 from '@/components/Child2'
import {mapState,mapMutations} from "vuex"
export default {
components:{Child2},
methods:{
...mapMutations([
// 将this.add 映射为 this.$store.commit("add")
"add"
]),
clickFn(){
this.add({n:10})
}
},
computed:{
...mapState(["count"])
}
}
</script>
数组就是不需要自定义触发事件名,自定义触发事件名和之前的触发事件名是同一个名字,也就是:
..mapMutations({
// 将this.add 映射为 this.$store.commit("add")
add:"add"
}),
简写为:
..mapMutations([
// 将this.add 映射为 this.$store.commit("add")
add:"add"
]),
Actions
actions选项的功能和mutations选项类似,但是也有不同之处:
- Actions提交的是mutation,而不是直接变更状态;
- Actions里面可以包含任意的异步操作;
所有的Action函数都接受一个与store实例具有相同方法和属性的context对象,这里的context对象指向当前的仓库,也就是store。因此在函数体中可以调用context.commit提交触发状态,相当于store.commit。这里注意,context对象不是store对象本身,这里相当于复制体。
action初体验
代码演示:
const store=new Vuex.Store({
state:{
count:0
},
mutations:{
// 声明一个mutation
add(state,obj){
state.count+=obj.n
}
},
actions:{
// 声明一个action
addOne(context){
context.commit("add",{n:10})
}
}
})
这样我们就声明好了一个action,再次强调:只接收一个参数,代表当前的仓库对象store。
由于是对象,我们可以采取ES6对象解构赋值的方式完成上面的操作。
const store=new Vuex.Store({
state:{
count:0
},
mutations:{
// 声明一个mutation
add(state,obj){
state.count+=obj.n
}
},
actions:{
// 声明一个action
addOne({commit}){
commit("add",{n:10})
}
}
})
这样就省略一些代码,同时也消除掉了无用的代码。(这里无用的代码指的是context,既然不对context对象进行任何操作,干嘛特意去声明它?)
action函数的触发
语法:store.dispatch(),注意,具体的触发方式和Mutation函数一样,分为普通触发(载荷触发)、对象方式触发。
这里面先把main.js的代码写好:
const store=new Vuex.Store({
state:{
count:0
},
mutations:{
// 声明一个mutation
add(state,obj){
state.count+=obj.n
}
},
actions:{
addOne(context){
context.commit("add",{n:10})
}
}
})
普通方式触发
语法:store.dispatch('action自定义事件名',实参),和mutation触发类似,第一个参数是自定义事件名,第二个参数是一个实参,通常是对象。
代码演示:
<template>
<div class="child">
我是Child组件
{{count}}
<button @click="clickFn">+10</button>
<Child2/>
</div>
</template>
<script>
import Child2 from '@/components/Child2'
import {mapState} from "vuex"
export default {
components:{Child2},
computed:{
...mapState(["count"])
},
methods:{
clickFn(){
// 触发action
this.$store.dispatch('addOne')
}
}
}
</script>
对象方式触发
语法:store.dispatch({type:"action自定义事件名",……键值对形式的实参……})。
代码演示:
<template>
<div class="child">
我是Child组件
{{count}}
<button @click="clickFn">+10</button>
<Child2/>
</div>
</template>
<script>
import Child2 from '@/components/Child2'
import {mapState} from "vuex"
export default {
components:{Child2},
computed:{
...mapState(["count"])
},
methods:{
clickFn(){
// 对象方式触发action
this.$store.dispatch({
type:"addOne"
})
}
}
}
</script>
小结:在组件中触发(分发)action的三种方式
在组件中分发,有辅助函数mapActions。
方式一:使用笨方法,this.$store.dispatch('action自定义事件名',实参)
<template>
<div class="child">
我是Child组件
{{count}}
<button @click="clickFn">+10</button>
<Child2/>
</div>
</template>
<script>
import Child2 from '@/components/Child2'
import {mapState} from "vuex"
export default {
components:{Child2},
computed:{
...mapState(["count"])
},
methods:{
clickFn(){
// 触发action
this.$store.dispatch('addOne')
}
}
}
方式二,使用mapActions模块,接收参数的类型是对象
<template>
<div class="child">
我是Child组件
{{count}}
<button @click="clickFn">+10</button>
<Child2/>
</div>
</template>
<script>
import Child2 from '@/components/Child2'
import {mapState,mapActions} from "vuex"
export default {
components:{Child2},
computed:{
...mapState(["count"])
},
methods:{
// 触发action
...mapActions({
addSomething:"addOne"
}),
clickFn(){
this.addSomething()
}
}
}
</script>
方式三,使用mapActions模块,接收参数的类型是数组
<template>
<div class="child">
我是Child组件
{{count}}
<button @click="clickFn">+10</button>
<Child2/>
</div>
</template>
<script>
import Child2 from '@/components/Child2'
import {mapState,mapActions} from "vuex"
export default {
components:{Child2},
computed:{
...mapState(["count"])
},
methods:{
// 触发action
...mapActions(["addOne"]),
clickFn(){
this.addOne()
}
}
}
</script>
对于以上三种触发方式,不再赘述,如果不动请参照Mutations选项。
了解action的返回结果
store.dispatch的返回结果就是Promise对象,因此也返回Promise对象。既然是Promise对象,我们可以在它触发action成功之后,做一些事情。
上代码:
// main.js
const store=new Vuex.Store({
actions:{
actionA({commit},amount){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
commit('increment')
resolve()
},1000)
})
}
}
})
<script>
// 组件
export default{
store.dispatch('actionA').then(()=>{
// 成功之后的回调
})
}
</script>
而且,我们也可以这么玩:在另一个action函数中,触发actionA:
const store=new Vuex.Store({
actions:{
actionA({commit},amount){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
commit('increment')
resolve()
},1000)
})
},
// 下面是actionB
actionB({commit,dispatch}){
return dispatch('actionA').then(()=>{
commit('otherMutation')
})
}
}
})
经过上面这么一折腾,action函数就显得非常灵活。小结一下,第一,我们可以Promise对象的特性,及时拿到成功、或者失败的反馈。第二,我们也可以在任何一个action函数中,处理其他action函数。
组合触发多个action
需求是这样的,在actionB函数中触发actionA函数,等触发完之后,再去做自己的事情。
// 假设 getData() 和 getOtherData() 返回的是 Promise
actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成
commit('gotOtherData', await getOtherData())
}
}
Modules
根据上面学习到的内容,一个store对象中包含状态(state)、状态处理(getters)、同步处理事件(mutations)、异步处理事件(actions)。我们就会看到store对象变得非常臃肿。为了解决这个问题,Vuex允许我们将store对象分割成多个模块,modules。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割。
modules初体验
需求:获取不同模块的状态,并且显示到页面
// main.js
// 3.创建一个仓库
const moduleA={
state:{
countA:"aaaaa"
}
}
const moduleB={
state:{
countB:"bbbbb"
}
}
const store=new Vuex.Store({
modules:{
aaa:moduleA,
bbb:moduleB
}
})
组件(.vue文件):
<template>
<div class="child">
我是Child组件
<br/>
{{aaa.countA}}
<br/>
{{bbb.countB}}
<Child2/>
</div>
</template>
<script>
import Child2 from '@/components/Child2'
import {mapState} from "vuex"
export default {
components:{Child2},
computed:{
...mapState(['aaa','bbb'])
}
}
</script>
效果图:
小结:
store.state.aaa表示moduleA的全部状态;- 这样分开写,代码逻辑更加清晰;
模块的局部状态
对于模块内部的getter函数和mutation函数,接收的第一个参数state,代表当前模块的局部状态对象。
Mutation代码演示
main.js
// 3.创建一个仓库
const moduleA={
state:()=>({
countA:"aaaaa"
}),
mutations:{
addOneA(state){
state.countA=state.countA.length
}
}
}
const moduleB={
state:{
countB:"bbbbb"
},
mutations:{
addB(state){
state.countB=state.countB+" 我是B组件的附加内容"
}
}
}
const store=new Vuex.Store({
modules:{
aaa:moduleA,
bbb:moduleB
}
})
组件文件:
<template>
<div class="child">
我是Child组件
<br/>
{{aaa.countA}}
<br/>
{{bbb.countB}}
<br/>
<button @click="clickFn">转变模块A的数据</button>
<Child2/>
</div>
</template>
<script>
import Child2 from '@/components/Child2'
import {mapState,mapMutations} from "vuex"
export default {
components:{Child2},
computed:{
...mapState(['aaa','bbb'])
},
methods:{
...mapMutations(['addOneA','addB']),
clickFn(){
this.addOneA()
this.addB()
}
}
}
</script>
按钮点击前的效果图:
按钮点击后的效果图:
getter函数接收三个参数,分别是state、getters、rootState,第二个参数是当前模块的其他getter函数,第三个参数是根节点状态。
Getter函数代码演示
main.js:
// 3.创建一个仓库
const moduleA={
state:()=>({
countA:"aaaaa",
arrA:[1,9,6,45,4]
}),
getters:{
// 过滤出大于1的数据
filterArrA(state,getters,rootState){
return rootState
}
}
}
const moduleB={
state:{
countB:"bbbbb"
}
}
const store=new Vuex.Store({
modules:{
aaa:moduleA,
bbb:moduleB
}
})
组件文件(.vue):
<template>
<div class="child">
我是Child组件
<br>
{{filterArrA}}
<Child2/>
</div>
</template>
<script>
import Child2 from '@/components/Child2'
import {mapGetters} from "vuex"
export default {
components:{Child2},
computed:{
...mapGetters(['filterArrA'])
}
}
</script>
效果图:
根节点状态就是,包含所有模块状态信息的对象。
对于action函数来说,接收一个对象参数,对象下面挂载state、commit、rootState等参数,分别代表局部状态、提交mutation、根节点状态。
action函数代码演示
const moduleA = {
// ...
getters: {
// 局部状态state、其他的getter、根节点状态
sumWithRootCount (state, getters, rootState) {
return state.count + rootState.count
}
}
}
命名空间
回顾一下mutation函数的代码展示:
main.js
// 3.创建一个仓库
const moduleA={
state:()=>({
countA:"aaaaa"
}),
mutations:{
addOneA(state){
state.countA=state.countA.length
}
}
}
const moduleB={
state:{
countB:"bbbbb"
},
mutations:{
addB(state){
state.countB=state.countB+" 我是B组件的附加内容"
}
}
}
const store=new Vuex.Store({
modules:{
aaa:moduleA,
bbb:moduleB
}
})
组件文件:
<template>
<div class="child">
我是Child组件
<br/>
{{aaa.countA}}
<br/>
{{bbb.countB}}
<br/>
<button @click="clickFn">转变模块A的数据</button>
<Child2/>
</div>
</template>
<script>
import Child2 from '@/components/Child2'
import {mapState,mapMutations} from "vuex"
export default {
components:{Child2},
computed:{
...mapState(['aaa','bbb'])
},
methods:{
...mapMutations(['addOneA','addB']),
clickFn(){
this.addOneA()
this.addB()
}
}
}
</script>
仔细观察组件文件中,触发mutation时,可以通过this.$store触发。这是问什么?
因为在默认情况下,模块内部的action、mutation、getter是注册到全局命名空间的。
如果想要代码有更高的封装性和复用性,还需要该模块称为具名的空间模块,实现方式是,在modules选项里面添加namespaced:true。当模块被注册后,它的所有相关的getter、mutation、action函数会根据注册时的路径,调整命名。
代码演示:
// 3.创建一个仓库
const moduleA={
namespaced:true,
state:()=>({
countA:"aaaaa",
arrA:[1,9,6,45,4]
}),
getters:{
// 获取根节点所有状态
filterArrA(state,getters,rootState){
return rootState
}
}
}
const store=new Vuex.Store({
modules:{
a:moduleA
}
})
.vue:
<template>
<div class="child">
我是Child组件
<br>
{{filterArrA}}
<Child2/>
</div>
</template>
<script>
import Child2 from '@/components/Child2'
import {mapGetters} from "vuex"
export default {
components:{Child2},
computed:{
// 注意获取方式
...mapGetters({filterArrA:['a/filterArrA']})
// filterArrA(){
// return this.$store.getters['a/filterArrA']
// }
}
}
</script>
效果图:
如果vuex是以模块化开发的,在组件文件中,获取数据的方法如下:
- state:
this.$store.state.模块名.key - getters:
this.$store.getters['模块名/key'] - mutations:
this.$store.commit('模块名/method') - actions:
this.$store.dispatch('模块名/method')
模块里面也可以进一步嵌套模块,原理是一样的。如果有namespaced:true属性,就通过父级模块名/子级模块名/getter方法名进行取值。如果有该属性,就通过父级模块名/getter方法名。
代码演示:
main.js
const store=new Vuex.Store({
modules:{
account:{
namespaced:true,
// 下面是模块内容,module assets
state:()=>({}),
getters:{
isAdmin(){} // getters的模块是getters['account/isAdmin']
},
actions:{
login(){} // dispatch('account/login')
},
mutations:{
login(){} // commit('account/login')
}
// 下面是嵌套模块,由于没有namespaced属性,所以继承父模块的命名空间
modules:{
myPage:{
state:()=>({}),
getters:{
profile(){} // getters['account/profile']
}
},
// 进一步嵌套
posts:{
namespaced:true,
state:()=>({}),
getters:{
popular(){} // getters['account/posts/popular']
}
}
}
}
}
})
借用了官网的demo,实现思路和前一个demo如出一辙,这里不再赘述。
如果启动了命名空间,模块的state、getter、mutation、action都是局部的。否则,就继承父级模块的命名空间。
在带命名空间的模块内访问全局内容(Global Assets)
如果你希望在带有namespaced属性的模块内使用全局的state、getter。在getter函数内,接收rootState、rootGetters作为第三、第四个参数,从而获得全局的state。在action函数内,也可以通过context对象的属性传入。
如果需要在命名模块内,分发action(dispatch)或者提交mutation(commit),将{root:true}做为第三个参数传给dispatch或者commit即可。
modules: {
foo: {
namespaced: true,
getters: {
// 在这个模块的 getter 中,`getters` 被局部化了
// 你可以使用 getter 的第四个参数来调用 `rootGetters`方法
someGetter (state, getters, rootState, rootGetters) {
getters.someOtherGetter // -> 'foo/someOtherGetter',调用foo模块下的其他getter方法
rootGetters.someOtherGetter // -> 'someOtherGetter',调用根节点下的getter方法
},
someOtherGetter: state => { ... }
},
actions: {
// 在这个模块中, dispatch 和 commit 也被局部化了
// 他们可以接受 `root` 属性以访问根 dispatch 或 commit
someAction ({ dispatch, commit, getters, rootGetters }) {
getters.someGetter // -> 'foo/someGetter'
rootGetters.someGetter // -> 'someGetter'
dispatch('someOtherAction') // -> 'foo/someOtherAction'
dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'
commit('someMutation') // -> 'foo/someMutation'
commit('someMutation', null, { root: true }) // -> 'someMutation'
},
someOtherAction (ctx, payload) { ... }
}
}
}
dispatch和commit的异同点?
相同点:
-
作用:commit 和 commit 两种方法都是提交mutation,进而改变状态;
-
语法
// 第一个参数:触发的mutation
// 第二个参数:载荷,可以是任何类型,推荐对象的形式
// 第三个参数:选项,类型对象形式
store.commit(type,payLoad,options)
不同点:
- 存取方式不同,commit用来提取当前模块的mutations,dispatch用来提交当前模块的actions(actions再去提交mutations),可以进行异步操作。因此,有些情况下,commit 做不到的,dispatch进行提交。
- commit的原理是直接通过mutations修改state;dispatch的原理则是通过action修改mutations,再通过mutations修改state。看起来效果都是修改了state,但是如果想在修改完做点其他事情,dispatch比较方便。
- 作用上更官方的解释是,commit提交mutation,dispatch分发action。提交mutation时,options 里可以有
root: true,它允许在命名空间模块里提交根的 mutation;分发action时,options 里可以有root: true,它允许在命名空间模块里分发根的 action。返回一个解析所有被触发的 action 处理器的 Promise。 - 简单来说,使用commit触发mutation,使用dispatch触发action。
小结
- 在具名模块中,getter函数接收四个参数,分别是state、getters、rootState、rootGetter,前两个分别代表着当前模块的state和getter,后两个分别代表着根节点的所有state和getter方法。
- 在具名模块中,action函数接受的context对象参数,下面有一个
rootGetters属性,里面保存着根节点的所有状态。 - 分发action或者提交mutation时,需要添加
{root:true}选项,作为第三个参数。