当一个组件要获取多个 state 的时候,声明计算属性就会变得重复和冗余了。我们可以使用到辅助函数 mapState 来更快更简洁地生成计算属性。
所以我们得清楚,mapState 的作用就是帮我们把一个对象或数组里的值转化成计算属性的写法。同理,其它的辅助函数也是大同小异,只要知道了 mapState 的实现,其它的也基本都明白了。
vuex 源码:深入 vuex 之辅助函数 mapState
mapState 的使用方式:
import { mapState } from 'vuex'
export default {
computed: mapState([
// 映射 this.count 为 store.state.count
'count'
])
}
mapState 返回一个对象,我们知道以上的代码最后会变成这样:
import { mapState } from 'vuex'
export default {
computed: {
count () {
return this.$store.state.count
}
}
}
state
state是什么? 定义:state(vuex) ≈ data (vue)
vuex的state和vue的data有很多相似之处,都是用于存储一些数据,或者说状态值.这些值都将被挂载 数据和dom的双向绑定事件,也就是当你改变值的时候可以触发dom的更新.
虽然state和data有很多相似之处,但state在使用的时候一般被挂载到子组件的computed计算属性上,这样有利于state的值发生改变的时候及时响应给子组件.如果你用data去接收$store.state,当然可以接收到值,但由于这只是一个简单的赋值操作,因此state中的状态改变的时候不能被vue中的data监听到,当然也可以通过watch $store去解决这个问题
综上所述,请用computed去接收state,如下
//state.js
let state = {
count: 1,
name: 'dkr',
sex: '男',
from: 'china'
}
export default state
<template>
<div id="example">
<button @click="decrement">-</button>
{{count}}
{{dataCount}}
<button @click="increment">+</button>
</div>
</template>
<script>
export default {
data () {
return {
dataCount: this.$store.state.count //用data接收
}
},
computed:{
count(){
return this.$store.state.count //用computed接收
}
}
methods: {
increment () {
this.$store.commit('increment')
},
decrement () {
this.$store.commit('decrement')
}
}
}
</script>
结果如下,用data接收的值不能及时响应更新,用computed就可以.
mapState 辅助函数
mapState是什么? 表面意思:mapState是state的辅助函数
实际作用:当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性
在使用mapState之前,要导入这个辅助函数.
import { mapState } from 'vuex'//没用default导出的要用{}
然后就是使用方式了
<template>
<div id="example">
<button @click="decrement">-</button>
{{count}}
{{dataCount}}
<button @click="increment">+</button>
<div>{{sex}}</div>
<div>{{from}}</div>
<div>{{myCmpted}}</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
data () {
return {
str: '国籍',
dataCount: this.$store.state.count
}
},
computed: mapState({
count: 'count', // 第一种写法
sex: (state) => state.sex, // 第二种写法
from: function (state) { // 用普通函数this指向vue实例,要注意
return this.str + ':' + state.from
},
// 注意下面的写法看起来和上面相同,事实上箭头函数的this指针并没有指向vue实例,因此不要滥用箭头函数
// from: (state) => this.str + ':' + state.from
myCmpted: function () {
// 这里不需要state,测试一下computed的原有用法
return '测试' + this.str
}
}),
methods: {
increment () {
this.$store.commit('increment')
},
decrement () {
this.$store.commit('decrement')
}
},
created () {
// 写个定时器,发现computed依旧保持了只要内部有相关属性发生改变不管是当前实例data中的改变,还是vuex中的值改变都会触发dom和值更新
setTimeout(() => {
this.str = '国家'
}, 1000)
}
}
</script>
在使用的时候,computed接收mapState函数的返回值, 可以用三种方式去接收store中的值,具体可以看注释.
事实上第二种和第三种是同一种,只是前者用了ES6的偷懒语法,箭头函数,在偷懒的时候要注意一个问题,this指针的指向问题,我已经在很多篇文章中提到不要在vue中为了偷懒使用箭头函数,会导致很多很难察觉的错误,如果你在用到state的同时还需要借助当前vue实例的this,请务必使用常规写法.
当然computed不会因为引入mapState辅助函数而失去原有的功能—用于扩展当前vue的data,只是写法会有一些奇怪,如果你已经写了一大堆的computed计算属性,做了一半发现你要引入vuex,还想使用mapState辅助函数的方便,你可以需要做下列事情.
//之前的computed
computed:{
fn1(){ return ...},
fn2(){ return ...},
fn3(){ return ...}
........
}
//引入mapState辅助函数之后
computed:mapState({
//先复制粘贴
fn1(){ return ...},
fn2(){ return ...},
fn3(){ return ...}
......
//再维护vuex
count:'count'
.......
})
从上述写法可以看出来,这不符合代码的某些说不明道不清的特性,我们希望我们可以不用去做一些复制粘贴的无用操作,而是直接使用mapState,希望它能自动融入到当前生产环境中,ok,ES6+(或者说ES7)提供了这个方便.
…mapState
事实上…mapState并不是mapState的扩展,而是…对象展开符的扩展.当然如果你把他用在这里会发现他能使得代码看起来变得,更加符合常规逻辑了,为什么这么说,你等下就会知道了.
首先,来回顾一下…对象展开符在数组中的表现,这在ES6语法学习分类里有相关说明,如果有兴趣可以关注我的ES6分类中的文章.
let arr = [1,2,3]
console.log(...arr) //1,2,3
然后来看一个例子.
let MapState = mapState({
count: 'count',
sex: (state) => state.sex
})
let json = {
'a': '我是json自带的',
...MapState
}
console.log(json)
这里的json可以成功将mapState return的json格式,和json自带的a属性成功融合成一个新的对象.你可以将这个称为对象混合
这样,你就可以自由的使用mapState了.
//之前的computed
computed:{
fn1(){ return ...},
fn2(){ return ...},
fn3(){ return ...}
........
}
//引入mapState辅助函数之后
computed:{
//原来的继续保留
fn1(){ return ...},
fn2(){ return ...},
fn3(){ return ...}
......
//再维护vuex
...mapState({ //这里的...不是省略号了,是对象扩展符
count:'count'
})
}
Vuex提供了state这样的状态统一管理树,你可以在vue中用computed计算属性接收这些公共状态,以便使用,当然你也可以在接收原值的基础上对这个值做出一些改造,如
computed:{
sex:function(){
return this.$store.state.sex + '加个字符串,算是改造'
}
}
但是如果你的其他组件也要使用这种改造方式去改造这个值,那你可能不得不去复制粘贴这个函数到别的组件中,当然,为了解决这个问题,vuex本身就提供了类似于计算属性的方式,getters可以让你从store的state中派生出一些新的状态,当然如果不是多个组件要用到这个状态,或者说每个子组件用到的派生属性不一样,那么,你完全可以不用getters.(这里多说一句吧,vuex的出现是为了解决组件间的通信问题,如果你的操作或者数据不涉及到公共操作,只是单一组件操作,请务必不要把这些状态值或者function存储到vuex中,因为vuex会把自身挂载到所有组件上,不管当前组件是否用到里面的东西,因此这事实上肯定增加了性能的损耗,注意是肯定,因为你很难保证每个子组件都用到同一个状态,除非是路由这样的特殊状态,当然路由的事情也无需归vuex管理,在后面vue-router系列中会讲到.)
mapState , …mapState用法解析 :
简单说一下我对mapState的理解,字面意思就是把store中state
的值遍历出来,任你取用,就不用写this.$store.getters.getState.openId等这么长的取值了,同理,mapMutations
mapGetters也是把store中对应的mutations getters方法遍历出来
下面上代码,看一下具体怎么操作
store.js代码
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
showLoading0: true,
showLoading1: true,
showLoading2: true,
showLoading3: true,
// ......
showLoading9: true,
},
// getters 适用于获取值,不会改变state原值
getters: {
getStore (state) {
return state
}
},
// 修改state
mutations: {
updateLoading (state, showLoading) {
state.showLoading = showLoading
}
},
actions: {
}
})
原来vue中的代码
<script>
export default {
data() {
return {
showLoading0: this.$store.getters.getStore.showLoading0,
showLoading1: this.$store.getters.getStore.showLoading1,
showLoading2: this.$store.getters.getStore.showLoading2,
showLoading3: this.$store.getters.getStore.showLoading3,
//......
showLoading9: this.$store.getters.getStore.showLoading9
}
},
methods: {
updateLoading() {
this.$store.commit('updateLoading', false)
}
}
}
</script>
由于this.$store
写法繁琐,所以mapState
的简化作用就凸显出来了
<script>
// 用之前要先引入vuex中的mapXXX方法
import { mapState } from 'vuex'
export default {
data() {
return {
showLoading0: (state) => state.showLoading0
showLoading1: (state) => state.showLoading1
showLoading2: (state) => state.showLoading2
showLoading3: (state) => state.showLoading3
// ......
showLoading9: (state) => state.showLoading9
}
}
}
</script>
// 用之前要先引入vuex中的mapXXX方法
import { mapState } from 'vuex'
export default {
//data() {
// return {
// }
//}
// 不用data接收了,直接用computed,使用和data里一模一样
// <h1 v-if="showLoading0">{{showLoading1}}</h1>
// console.log(this.showLoading9)
computed: {
...mapState([
'showLoading0','showLoading1',....,'showLoading9'
])
}
}
mapMutations mapGetters的使用也是同理
// 用之前要先引入vuex中的mapXXX方法
import { mapState } from 'vuex'
export default {
// 使用 this.getStore()
computed: {
...mapGetters([
'getStore'
])
},
// 使用this.updateLoading()
// 就相当于this.$store.commit('updateLoading')
methods: {
...mapMutations([
'updateLoading'
]),
}
}
其实最好采用computed
的方式取值,这样会避免很多问题的发生,特别是数据更新不及时
实例 :
// @/store/index.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export const store = new Vuex.Store({
// state专门用来保存 共享的状态值
state: {
// 保存登录状态
login: false
},
// mutations: 专门书写方法,用来更新 state 中的值
mutations: {
// 登录
doLogin(state) {
state.login = true;
},
// 退出
doLogout(state) {
state.login = false;
}
}
});
<!--@/components/Header.vue-->
<script>
// 使用vux的 mapState需要引入
import { mapState } from "vuex";
export default {
// 官方推荐: 给组件起个名字, 便于报错时的提示
name: "Header",
// 引入vuex 的 store 中的state值, 必须在计算属性中书写!
computed: {
// mapState辅助函数, 可以快速引入store中的值
// 此处的login代表, store文件中的 state 中的 login, 登录状态
...mapState(["login"])
},
methods: {
logout() {
this.$store.commit("doLogout");
}
}
};
</script>
getters
Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。说白了就是vue的computed,如果你了解computed的话,那你可以像使用computed一样去使用getters,当然还是有点区别的.
//state.js
let state = {
from: 'china',
arr: [2, 3, 1, 4, 6]
}
export default state
// getters.js
// 第一个参数是state
let address = (state) => {
return '国籍:' + state.from
}
// 第二个参数可以访问getters
let addressMore = (state, getters) => {
return '其他描述' + getters.address
}
// return 一个function,这个function可以传参,当然这个function最后会返回一个具体的数值
//本例中这个方法用于查询state中的arr数组是否存在某个值
let findArr = (state) => (number) => {
let ifExit = state.arr.find((n) => n === number) // arr.find是ES6语法中数组的扩展
if (typeof (ifExit) === 'undefined') {
return false
} else {
return true
}
}
export {address, addressMore, findArr}
关于getters如何使用,可以看一下上面代码的注释,这里我重点介绍一下getters和computed的不同,就是上面的第三种用法,我之前在vue进阶系列中探讨过computed,filters两种数据处理工具的局限性,有兴趣的可以去看这篇文章,computed的一个缺点就是不能传参,假设你要去判断一个数组里是否存在某个值,那你没法将某个值传到computed中去,这其实是一个很蛋疼的事情,当然你可以通过某些特殊手段,这里我不展开,有兴趣的可以留言.而getters则没有这个烦恼,有些对ES6语法使用较为吃力的同学可以看下面的简易版本,来看看findArr究竟做了什么.
let findArr = function(state){
// 返回一个匿名函数
return function(number){
// 如果有相同的则返回n,如果找不到则返回undefined
let ifExit = state.arr.find(function(n){
return n===number
})
if (typeof (ifExit) === 'undefined') {
return false
} else {
return true
}
}
}
最后我们在子组件中展示一下效果
<template>
<div>
<div>{{from}}</div>
<div>{{from2}}</div>
</div>
</template>
<script>
// import { mapGetters } from 'vuex'
export default {
computed: {
from: function () {
return this.$store.getters.address
},
from2: function () {
return this.$store.getters.addressMore
}
},
created () {
console.log(this.$store.getters.findArr(2))
console.log(this.$store.getters.findArr(7))
}
}
</script>
结果如下所示.
mapGetters 辅助函数
关于辅助函数的使用和对象展开符的使用我在本系列的第二章中已经说的很明白了,有兴趣的可以看一下这个链接
<template>
<div>
<div>{{from}}</div>
<div>{{from2}}</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
computed: mapGetters({
'from': 'address',
'from2': 'addressMore',
'find': 'findArr'
}),
created () {
console.log(this.find(1)) // 由于getters已经通过computed挂载到当前实例,所以你不需要再通过this.$store.getters的方法去访问
console.log(this.$store.getters.findArr(2))
console.log(this.$store.getters.findArr(7))
}
}
</script>
…mapGetters
<template>
<div>
<div>{{from}}</div>
<div>{{from2}}</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters({
'from': 'address',
'from2': 'addressMore',
'find': 'findArr'
})
},
created () {
console.log(this.find(1)) // 由于getters已经通过computed挂载到当前实例,所以你不需要再通过this.$store.getters的方法去访问
console.log(this.$store.getters.findArr(2))
console.log(this.$store.getters.findArr(7))
}
}
</script>
最后在说一句,很多情况下你是用不到getters的,请按需使用,不要用getters去管理state的所有派生状态,如果有多个子组件或者说子页面要用到,才考虑用getters.