Vuex的终极理解

554 阅读3分钟

vuex.png

这张图记住!!!!!!!!!!!!!!!!!!!!!

一、state

标准的store目录结构

引入vuex以后,我们需要在state中定义变量,类似于vue中的data,通过state来存放共享的状态

--store
 --actions
 --mutations
 --getters
 --mutations-type
 --index.js
 --state

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: 'fufu',
    age: 21,
    sex: '女'
  },
  likes: '101'
}

组件使用创建项目初始的就行,App.vue自定义的那两个

<template>
  <div id="app">
    <div id="nav">
      <router-link to="/">Home</router-link> |
      <router-link to="/about">About</router-link>
    </div>
    <router-view />
  </div>
</template>

这个不是重点!随便整一个就行

about.vue里的内容

<h1>{{ $store.state.userInfo.userName }}</h1>

运行结果:

image-20211214151223133.png

显示出了相应的内容,有了vuex,我们不必再考虑组件之间的传值,直接就可以通过$store来获取不同的数据。

但是如果需要vuex中的多个数据的时候,这样写很麻烦,可以直接将它定义在computed中。

props,methods,datacomputed的初始化都是在beforeCreatedcreated之间完成的。

例如:

<template>
  <div>
    {{ userAge }}
  </div>
</template><script>
export default {
  name: "Home",
  computed: {
    userAge() {
      return this.$store.state.userInfo.age;
    },
  },
};
</script>

mapState辅助函数

使用this.$store.state虽然可以很方便的将state里面的值融入computed,但是如果要取多个值,就会出现以下的情况

computed:{
    userInfo(){
      return this.$store.state.userInfo
    },
    likes(){
     return this.$store.state.likes
    },
    ...
  }

特别多的话就会很麻烦,而这时候vuex又给我们提供了更简便的方法mapState方法,

该方法就可以自动将需要的state值映射为实例的计算属性

我们可以这样写:

<template>
  <div>
    {{ userInfo.sex }}
  </div>
</template><script>
import { mapState } from "vuex";
export default {
  name: "Home",
  computed: mapState(["userInfo", "likes"]),
};
</script>

这里的computed: mapState(["userInfo", "likes"])等价于:

computed:{
    userInfo(){
      return this.$store.state.userInfo
    },
    likes(){
     return this.$store.state.likes
    },
  }

记住:用mapState等这种辅助函数的时候,前面的方法名和获取的属性名是一致的。

还有个起别名的功能,简单说一下

computed: mapState({
    myInfo: "userInfo",
    myLike: "likes"
}),

如果要自定义一个计算属性怎么办呢?怎么添加?

现在已经写成:computed: mapState(["userInfo", "likes"])这样了

现在就要用到es6的新特性:...扩展符

computed: {
  name() {
    return '123'
  },
  ...mapState(["userInfo", "likes"])
}

二、getters

getters相当于vue中的计算属性,能拿到state里面最新的值

而且getters允许传参,第一个参数就是state

这样,通过getters进一步处理,得到想要的值,

getter.js

export default {
  realAge: (state) => {
    return state.userInfo.age + 1
  },
  realLike: (state) => {
    return state.likes + '下棋'
  }
}

Home.vue

<template>
  <div>{{ realAge }} -- {{ realLike }}</div>
</template><script>
export default {
  name: "Home",
  computed: {
    realAge() {
      return this.$store.getters.realAge;
    },
    realLike() {
      return this.$store.getters.realLike;
    },
  },
};
</script>

运行结果:

image-20211214160144480.png

mapGetters辅助函数

mapGetters函数具有mapState的作用,而且其主要用法也是一样的,也能将getters的属性映射到实例的计算属性

computed: {
  ...mapGetters(["realAge", "realLike"]),
},

同样也可以取别名,这里不写了,上面有个例子^ ^

会发现其实gettersstate实际上差不多,但是最大的区别是getters可以传参,$store.state.userInfo.age只能得到固定的值,但是我们$store.getters.realAge得到的值是加工后的值,所以用gettes可以满足更多的数据需求吧。

当然,如果不需要对数据进行加工,$store.getters = $store.state

三、mutations

代码中定义的时候需要mutations,它类似于vue中的methods

mutations需要通过commit来调用其里面的方法,它也可以传入参数,第一个参数是state,第二个参数是载荷(payLoad),也就是额外的参数。

我们只能通过mutation去更改state里面的值

mutations.js

export default {
  addAge: (state, payLoad) => {
    state.userInfo.age += payLoad.number
  }
}

Home.vue

<template>
  <div>
    <div>{{ realAge }} -- {{ realLike }}</div>
    <button @click="handleAddAge">增加一岁</button>
  </div>
</template><script>
import { mapGetters } from "vuex";
export default {
  name: "Home",
  computed: {
    ...mapGetters(["realAge", "realLike"]),
  },
  methods: {
    handleAddAge() {
      this.$store.commit("addAge", { number: 1 });
    },
  },
};
</script>

运行出来的效果就是,点击按钮,年龄+1

使用mutation不像之前state、getters一样直接调用,而是要用关键字commit来提交mutation

调用的时候第二个参数最好写成对象形式,这样可以传递更多的信息:

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'
 })
}

这时候会有疑问,我为什么要绕一圈,从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'export default {
  [ADD_AGE](state, payLoad) {
    state.userInfo.age += payLoad.number
  }
}

为什么这样写?

  • 不容易写错,字符串容易写错,而且字符串写错以后不会报错位置,而用常量替代,如果写错,eslint可以提示错误位置
  • 当使用action派发mutation时,在action中使用同样的常量,避免手滑写错方法

并不是非得这样做

用常量替代mutations的时候我我们可以新建一个文件(mutation_type.js)专门存储这些常量

mutations_type.js

const ADD_AGE = 'addAge'export{
  ADD_AGE
}

注意这里是export不是export default

mutations.js

import {ADD_AGE} from './mutations-type'export default {
  [ADD_AGE](state, payLoad) {
    state.userInfo.age += payLoad.number
  }
}

四、actions

action类似于mutation

我们只需要记住一下几点:

  • action可以提交mutation,然后mutation去更改state
  • action不要直接去操作state,而是去操作mutation
  • action包含异步操作,类似于axios请求,可以都放在action中写
  • action中的方法默认的就是异步,并且返回promise

为什么?因为这是Vuex规定的

举个例子:

actions.js

import {ADD_AGE} from './mutations-type'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( ADD_AGE,{number: res.number}
       )
    },
}

也可以写setTimeOut

actions.js

import {ADD_AGE} from './mutations-type'export default {
  setAge(context) {
    setTimeout(() => {
      context.commit(ADD_AGE, {number: 1})
    }, 2000)
  }
}

还可以用解构赋值的方式对context里的属性进行解构

import {ADD_AGE} from './mutations-type'export default {
  setAge({ commit }) {
    setTimeout(() => {
      commit(ADD_AGE, {number: 1})
    }, 2000)
  }
}

这样就ok了

Home.vue

<template>
  <div>
    <div>{{ realAge }} -- {{ realLike }}</div>
    <button @click="setAge({ number: 1 })">增加一岁</button>
  </div>
</template><script>
import { mapGetters } from "vuex";
export default {
  name: "Home",
  computed: {
    ...mapGetters(["realAge", "realLike"]),
  },
  methods: {
    setAge() {
      this.$store.dispatch("setAge");
    },
  },
};
</script>

梳理一下流程:

  1. 页面初始化的时候...省略

  2. 点击按钮调用this.setAge()方法,this.setAge()派发(dispatch)一个名为setAgeaction

  3. setAge这个action中,执行如下操作:

    setAge(context) {
      setTimeout(() => {
        context.commit(ADD_AGE, {number: 1})
      }, 2000)
    }
    
  4. age发生了改变,触发computed重新计算,然后拿到了age最新的值

所以看到这里,应该明白Vuex所谓的单向数据流

界面——>派发action——>action提交mutation——>mutation更改state——>getters返回最新的state值到界面

mapActions跟上面的差不多,不写了。

要提一下的是一个action里也可以dispatch其他action

setAge(context) {
  setTimeout(() => {
    context.commit(ADD_AGE, {number: 1})
    context.dispatch('xxx')
  }, 2000)
}

五、总结

  1. 直接获取state的数据,用this.$store.state.值
  2. 依赖state得到新的数据,用this.$store.getters.值
  3. 同步修改state的属性值,就用this.$store.commit('mutation值', payLoad)
  4. 异步修改state的属性值,就用this.$store.dispatch('action值')
  5. mapState、mapMutations、mapGetters、mapActions等辅助函数,便于我们处理多个方法和属性