Vue学习(五)——Vuex核心概念(State、Getter、Mutation、Action、Module)

前言

 上篇文章展示了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本身内容不多,其他暂时不打算讲~~~~~~

猜你喜欢

转载自blog.csdn.net/sadoshi/article/details/121862380