[toc]
在写这篇文章之前,在使用Vuex开发的时候,总是一知半解的,不会的就百度,复制粘贴后能跑就行,对于其里面的细节,也不是很了解。
今天借着项目踩坑的时机,查了很多资料,于是想把Vuex从头到尾说清楚。
看该文章之前,希望你对Vue和Vuex的有一定的基础知识,如果没有,请移步Vue官网
里面难免写的有错误,各位大佬看官不要介意,希望能指出错误~~
一 、state
先看一下标准的store目录结构
入vuex 以后,我们需要在state中定义变量,类似于vue中的data,通过state来存放共享的1状态
-store
--actions
--mutations
--getters
--mutations-type
--index.js
index.js是总入口
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
import state from './state.js'
import actions from './actions.js'
import mutations from './mutations.js'
import getters from './getters.js'
export default new Vuex.Store({
state,
actions,
mutations,
getters
})
我们在state.js中定义变量
export default {
userInfo:{
userName:"油炸皮卡丘",
age:"22",
sex:"女"
},
token:"aerfrftregtyrb.rytyuhjyi",
friends:["Tom","Herry","Lucy"],
likes:["吃饭",'睡觉','看电影'],
money:250
}
在组件中使用,在App.vue中自定义两个组件
<div id="app">
<about> </about>
<home> </home>
</div>
about.vue里面的内容
<h1>{{$store.state.userInfo.userName}}</h1> //油炸皮卡丘
home.vue里面的内容
<h1>{{$store.state.userInfo.age}}</h1> //22
如图,显示出显示出相应的内容,有了vuex,我们不必在考虑组件之间的传值,直接就可以通过$store来获取不同的数据
但是如果需要vuex中的多个数据的这时候,这样写就太啰嗦了,我们可以将它定义在computed中。
props
,methods
,data
和computed
的初始化都是在beforeCreated
和created
之间完成的。
例如
<template>
<div class="home">
{{userName}}
</div>
</template>
<script>
export default {
name: 'home',
computed:{
userName(){
return this.$store.state.userInfo.userName
}
}
}
</script>
这样引入就方便了很多。
1.2 mapState辅助函数
使用this.$store.state
虽然可以很方便的将state里面的值融入computed,但是如果要取多个值,就会出现以下的情况
computed:{
userInfo(){
return this.$store.state.userInfo
},
token(){
return this.$store.state.token
},
friends(){
return this.$store.state.friends
},
likes(){
return this.$store.state.likes
},
...
}
特别多的话就会很麻烦,而这时候vuex又给我们提供了更简便的方法mapState
方法,
该方法就可以自动将需要的state值映射为实例的计算属性
我们可以这样写
import {mapState} from 'vuex'
export default {
name: 'home',
computed: mapState(['likes','friends','token','userInfo'])
}
mapState(['likes','friends','token','userInfo']) //填入哪些字段就会将这些字段自动映射为一个计算属性
所以
mapState(['likes','friends','token','userInfo'])
等价于下面的代码
userInfo(){
return this.$store.state.userInfo
},
token(){
return this.$store.state.token
},
friends(){
return this.$store.state.friends
},
likes(){
return this.$store.state.likes
},
记住:用mapState等这种辅助函数的时候,前面的方法名和获取的属性名是一致的。
如果我们需要自定义一个计算属性怎么办呢?怎么添加呢?
毕竟现在已经成这样了 computed: mapState(['likes','friends','token','userInfo'])
这时候我们就需要es6中的展开运算符:...。将mapState映射的计算属性追加上去,而不是覆盖原有的
computed: {
value(){
return this.val++
},
...mapState(['likes','friends','token','userInfo'])
}
在模板中使用
<h2>{{userInfo.userName}}</h2> //油炸皮卡丘
关于别名
有时候,我们映射的时候,想给计算属性起个新的名字,不想用原有的属性名,那就可以像下面这样做,用对象的形式去别名
computed: {
value(){
return this.val++
},
...mapState({
myLikes:"likes",
myFriends:"friends",
theUserInfo:"userInfo"
})
}
这样我们就可以在模板中使用
<h2>{{theUserInfo.userName}}</h2> //油炸皮卡丘
二、getters
getters相当于vue中的计算属性,能拿到state里面最新的值
而且getters允许传参,第一个参数就是state
这样,通过getters进一步处理,得到我们想要的值,
getters.js
export default{
realName:(state)=>{
return state.userInfo.userName+''
},
myMoney:(state)=>{
return (state.money/7).toFixed(2)
}
}
在实例中我们可以这样用
computed: {
valued(){
return this.value++
},
realName:this.$store.getters.realName,
myMoney:this.$store.getters.myMoney,
}
2.2 mapGetters辅助函数
mapGetters函数具有mapState的作用,而且其主要用法也是一样的,也能将getters的属性映射到实例的计算属性
computed: {
valued(){
return this.value++
},
...mapGetters(['realName','myMoney'])
}
同样也可以取别名
computed: {
valued(){
return this.value++
},
...mapGetters({
myRealName:"realName",
myFormatMoney:"myMoney"
})
}
但有人就要问了,二者的作用实际上都一样?到底用哪个?或者说二者还有什么实质上的区别?
上面说过,getters可以传参,这才是getters和mapState的区别
那么如何传参呢?
getters的第一个参数是state
有这样一个场景,我们不想取所有的state里面的friends值,而是想根据我们传入的值筛选一下
那我们只要在getter返回的值套上一层函数那我们就可以实现传参数
export default{
realName:(state)=>{
return state.userInfo.userName+''
},
myMoney:(state)=>{
return (state.money/7).toFixed(2)
},
myFriend:(state)=>{
//返回一个函数用于接收我们传入的筛选值
return function (friendName) {
return state.friends.find(item => item === friendName)
}
}
}
很显然,getters不仅可以像mapState一样拿到state里面的值,而且还可以在拿到之前,对值进行我们想要的加工
从而“派生”一些新的值,以满足我们不同的业务需要
淡然,如果不需要派生新的值,
this.$store.getters.值 就等于 this.$store.state.值
三、mutation
我们代码中定义的时候需要些mutations,它类似于vue中的methods,
mutations需要通过commit来调用其里面的方法,它也可以传入参数,第一个参数是state,第二个参数是载荷(payLoad),也就是额外的参数
我们只能通过mutation去更改state里面的值
mutations.js
export default {
addAge:(state,payLoad)=>{
state.userInfo.age+=payLoad.number
},
changeName:(state,payLoad)=>{
state.userInfo.userName+=payLoad.userName
},
addFriend:(state,payLoad)=>{
if(state.friends.indexOf(payLoad.newFriend) < 0){
state.friends.push(payLoad.newFriend)
}
},
setUserInfo:(state,payLoad)=>{
state.userInfo=payLoad.userInfo
},
setToken:(state,payLoad)=>{
state.token=payLoad.token
}
}
在模板中使用
<div class="home">
<button @click="handleAddAge">增加一岁</button>
<button @click="handleAddFriend">增加一个朋友</button>
</div>
js部分
methods:{
handleAddAge(){
//使用mutation不像之前state、getters一样直接点调用,而是用commit关键字来提交mutation
this.$store.commit('addAge',{
number:1
})
},
handleAddFriend(){
let name="皮卡丘";
this.$store.commit('addFriend',{
newFriend:name
})
}
}
调用的时候第二个参数最好写成对象形式,这样我们就可以传递更多信息。
this.$store.commit('mutationName',{
key1:val1,
key2:val2,
key3:val3
})
但是,这样写还是会遇到同样的问题,就是如果需要操作多个数据,就会变的麻烦,这时候我们就需要mapMutations,通过它将方法映射过来
3.1 mapMutations辅助函数
跟mapState、mapGetters一样
不同的是,mapMutations是将所有mutations里面的方法映射为实例methods里面的方法
所以我们可以这样用
methods:{
...mapMutations(['addAge'])
}
mapMutations(['addAge'])这一句就相当于下面的代码
methods:{
addAge(payLoad){
this.$store.commit('addAge',payLoad)
}
}
参数我们可以在调用这个方法的时候写入
<button @click="AddAge({number:1})">增加一岁</button>
同样也可以有别名
methods:{
...mapMutations({
handleAddAge:'addAge'
})
}
<button @click="handleAddAge({number:1})">增加一岁</button>
这时候有些人要说了,我为什么要绕一圈,从mutations里面去改state呢?我能不能直接改state呢?
比如这样:
addAge(){
this.$store.state.userInfo.age +=5;
}
实际看结果也可以,那我为什么从mutations里面中转一下呢?
原因如下:
-
在mutations中不仅仅能做赋值操作
-
Vue.js在mutations中做了类似埋点操作,如果从mutations中操作的话, 能被检测到,可以更方便用调试工具调试,调试工具可以检测到实时变化,而直接改变state中的属性,则无法实时监测
注意:mutations只能写同步方法,不能写异步,比如axios、setTimeout等,这些都不能写,mutations的主要作用就是为了修改state的。
原因类似:如果在mutations中写异步,也能够调成功,但是由于是异步的,不能被调试工具追踪到,所有不推荐这样写,不利于调试,这是官方的约定。
3.2 使用常量替代Mutation事件类型
把原本的方法名称由字符串转变成常量
mutations.js
const ADD_AGE ='addAge'
const CHANGE_NAME ='changeName'
const ADD_FRIEND='addFriend'
const SET_USER_INFO='setUserInfo'
const SET_TOKEN='setToken'
//然后用常量替换原有的方法名
export default {
[ADD_AGE](state,payLoad){
state.userInfo.age+=payLoad.number
},
[CHANGE_NAME](state,payLoad){
state.userInfo.userName+=payLoad.userName
},
[ADD_FRIEND](state,payLoad){
if(state.friends.indexOf(payLoad.newFriend) < 0){
state.friends.push(payLoad.newFriend)
}
},
[SET_USER_INFO](state,payLoad){
state.userInfo=payLoad.userInfo
},
[SET_TOKEN]:(state,payLoad)=>{
state.token=payLoad.token
}
}
为什么这样写?
- 不容易写错,字符串容易写错,而且字符串写错以后不会报错位置,而用常量替代,如果写错,eslint可以提示错误位置
- 当使用action派发mutation时,在action中使用同样的常量,避免手滑写错方法
用常量替代mutations的时候我我们可以新建一个文件(mutation_type.js)专门存储这些常量
mutation_type.js部分
const ADD_AGE ='addAge'
const CHANGE_NAME ='changeName'
const ADD_FRIEND='addFriend'
const SET_USER_INFO='setUserInfo'
const SET_TOKEN='setToken'
export {
ADD_AGE,
CHANGE_NAME ,
ADD_FRIEND,
SET_USER_INFO,
SET_TOKEN
}
然后在mutations.js中引入
import {
ADD_AGE,
CHANGE_NAME,
ADD_FRIEND,
SET_USER_INFO,
SET_TOKEN
} from "./mutation_type.js"
export default {
[ADD_AGE](state,payLoad){
state.userInfo.age+=payLoad.number
},
[CHANGE_NAME](state,payLoad){
state.userInfo.userName+=payLoad.userName
},
[ADD_FRIEND](state,payLoad){
if(state.friends.indexOf(payLoad.newFriend) < 0){
state.friends.push(payLoad.newFriend)
}
},
[SET_USER_INFO](state,payLoad){
state.userInfo=payLoad.userInfo
},
[SET_TOKEN]:(state,payLoad)=>{
state.token=payLoad.token
}
}
四、actions
action类似于mutation
我们只需要记住一下几点:
- action可以提交mutation,然后mutation去更改state
- action不要直接去操作state,而是去操作mutation
- action包含异步操作,类似于axios请求,可以都放在action中写
- action中的方法默认的就是异步,并且返回promise
为什么?因为这是Vuex规定的
actions.js
import {
ADD_AGE,
CHANGE_NAME,
ADD_FRIEND,
SET_USER_INFO,
SET_TOKEN
} from "./mutation_type.js"
export default{
//定义一个异步获取用户信息的action
async getUserInfo(context){
//context可以理解为它是整个Store的对象.类似于this.$store,里面包含了state,getter,mutations,actions
const res = await axios.get('/接口url')
//这个时候就用到了 mutation_type.js
context.commit( SET_USER_INFO,{userInfo:res.userData}
)
},
//定义一个异步获取用户token的action
async getToken(context){
const res = await axios.get('/接口url')
context.commit(
SET_TOKEN,
{token:res.token}
)
}
}
当然,我们可以通过解构的方式,将context对象里面的属性解构
async getUserInfo:({commit,dispatch})=>{
const res = await axios.get('/接口url')
commit( SET_USER_INFO, {userInfo:res.userData})
}
想想一个实际开发场景,state里面的userInfo属性值是空的,当登录以后,再进行获取对应的信息。
登录以后,需要得到用户信息显示到界面上,那如何得到呢?
首先进入页面的时候调用actions中的getUserInfo方法
vue部分
<template>
<div>{{realName}}</div>
</template>
export default{
computed(){
//第三步
realName:this.$store.getters.realName
},
created(){
//第一步
this.reqUserInfo()
},
methods:{
reqUserInfo(){
//第二步使用action不想之前state、getters一样直接点调用,而是用dispatch关键字来派发action
this.$store.dispatch('getUserInfo')
}
}
}
来梳理一下上面的流程
- 页面初始化时调用
this.reqUserInfo()
方法。this.reqUserInfo()
派发一个名为getUserInfo
的action - 在
getUserInfo
这个action中,我们执行了如下操作:
async getUserInfo(context){
//1.从接口里异步获取数据
const res = await axios.get('/接口url')
//2.将获取到的数据,通过commit提交一个mutation,去更改state里面的userInfo
context.commit( SET_USER_INFO,{userInfo:res.userData})
},
- 在computed中拿到最新的用户信息的用户名
//3.拿到最新的userInfo
realName:this.$store.getters.realName
所以看到这里,大家应该明白Vuex所谓的单向数据流
界面——>派发action——>action提交mutation——>mutation更改state——>getters返回最新的state值到界面
4.1 mapActions辅助函数
mapActions和mapMutations一样,它将actions里面的方法映射到methods
所以,当action多了,我们可以这样使用
methods:{
...mapActions(['getUserInfo','getToken'])
}
相当于
methods:{
getUserInfo(){
return this.$store.dispatch(‘getUserInfo’)
},
getToken(){
return this.$store.dispatch(‘getToken’)
},
}
当然也可以有别名
methods:{
...mapActions(
{reqUserData:'getUserInfo'},
{reqToken:'getToken'},
)
}
另外,补充一下
可以在action派发别的acton,如下
async getUserInfo(context){
const res = await axios.get('/接口url')
context.commit( SET_USER_INFO,{userInfo:res.userData})
//此处派发另外一个action
context.dispatch('getToken')
},
5、总结一下
- 依赖state得到新的数据,用this.$store.getters.值
- 直接获取state的数据,用this.$store.state.值
- 同步修改state的属性值,就用this.$store.commit('mutation值')
- 异步修改state的属性值,就用this.$store.dispatch('action值')
- mapState、mapMutations、mapGetters、mapActions等辅助函数,便于我们处理多个方法和属性