Vuex怎么学(二)
- 一个项目做完了,回头再来看看vuex这个坑,以加深对vuex的印象,如果你有兴趣,那么一起来看吧~~
坑一:vuex公共状态管理与vuex状态渲染机制的坑
- 先来看一段代码,理论上,count1(第一个p标签的值)和count2(第二个p标签的值)显示的数字应该是相同的,但实际上,只有count2能保持实时刷新
- 这里分析下原因:
- count1是用组件内声明的变量去接收vuex内的公共状态,页面首次渲染可以成功得到state中的状态,但后续就不再去state中拿数据了。这里在data中接收公共状态的数据,相当于是个一次性赋值的操作
- count2直接在{{}}中调用state中的数据,当state中的数据发生改变,count2必然会发生改变,这样就实现了当前数据与公共状态中的数据保持一致
// store.js
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
count: 10,
secCount: 10086
},
getters: {
// getter 返回一个函数 add(num)
add: (state, getter) => (num) => {
return state.count + num;
},
// getter 返回一个数值 app
app: (state, getter) => {
return state.count - 10;
}
},
mutations: {
plus(state, num) {
state.count += num;
},
minus(state, num) {
state.count -= num;
},
printf(state, res) {
console.log('调用mutions中的printf方法打印: ', res);
}
},
actions: {
actionA({ dispatch, commit }) {
setTimeout(function () {
console.log('延时1s打印actionA')
}, 1000);
},
actionB({ dispatch, commit }) {
axios.get('../../static/data.json').then((res) => {
console.log('成功获取数据: ');
commit('printf',res.data);
}).catch((err) => console.log('失败了!',err));
}
}
});
export default store;
// demo01.vue
<template>
<div class="hello">
<p>count1: {{ count }}</p>
<p>count2: {{ $store.state.count }}</p>
<button @click="plus">点击+2</button>
</div>
</template>
<script>
export default {
data () {
return {
count: this.$store.state.count
}
},
methods:{
plus(){
console.log('点击+2');
this.$store.commit('plus', 2);
}
}
}
</script>
- 点击几次增加数字的效果如下
- 这里提醒朋友们注意不要因为这个不起眼的细节导致你的vuex状态达不到效果
- 那有朋友就问啦,没有办法使count1的形式也达到实时渲染公共状态的效果呢?毕竟项目大的话,每次都要写
$store.state.XXX
是一件很麻烦的事情,也很难看!!!这里我告诉你,有办法, 向下看!!!
先补一个ES6的新东西,以免新人看下面的坑不明所以
- 我们使用Vue开发项目一般都是用webpack快速构建,好处是可以无后顾之忧的使用ES6和ES7的新语法,新语法有很多,这里我只说两个,便于大家看后边的东西,其余的在后边的学习工作中在慢慢以博客形式更新
- 第一个:ES6的promise异步处理。下面的实例中,用到了一个轻量级的ajax库
axios
,它请求完成返回的就是一个promise对象,promise对象只有三种状态(异步执行中pending,执行成功resovled,执行失败rejected),并且在同一时刻只可能处于其中的一种状态。在axios中,执行成功会调用then()方法,失败会被catch()方法捕捉
axios.get('../../static/data.json') // pending 执行中的promise
.then((res) => { // resovled 执行成功
console.log('成功获取数据: ');
commit('printf',res.data);
})
.catch((err) => console.log('失败了!',err)); // rejected 执行失败
- 第二个:扩展运算符
...对象或数组
。作用是不使用遍历、contact方法等,就可以快捷的将数组(或对象)混入到另一个或多个数组(或对象中),例如
let ar1 = [1, 3], ar2 = ['a', 'b'];
let arr = ['abc', ...ar1, ...ar2, 456];
console.log(arr); // ['abc', 1, 3, 'a', 'b', 456]
let obj1 = {a: 1, b: 3}, obj2 = {aaa: 345};
let obj = {...obj1, qqq: 'aqa', ...obj2, aaa: 0};
console.log(obj); // {a: 1, b: 3, qqq: 'aqa', aaa: 0} 注意,这里的aaa的值被后面的覆盖为0
坑二:vuex的map辅助函数:mapState 、mapGetters、mapMutations、mapActions
- 说起来呀,这个坑还真的是个磨人的老妖精呐。
- 学习了一阵子ES6的我天真的以为这里的mapXXX跟ES6的对象的map方法一样,官方文档也写得太简单并且没有案例,折腾了老半天,一无所获-,-|
- 我又继续往下看,打算看完还是不懂就放弃,反正不用这mapXXX也可以。但是,当我看到mapMutations和mapActions时,突然看到了这么一句,豁然开朗,这里的mapXXX不是简单的对象关系映射,而是用来映射对应的状态到vue组件中,作用主要是用来精简代码,将store中的状态映射到组建中,绑定到该组件的Vue对象上(this指针),例如下面的
this.increment()
相比this.$store.dispatch('increment')
少了好多个字母是吧(发现新大陆,满满的成就感:)
...mapActions([
'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`
// `mapActions` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
]),
- 举一反三,我们自己来练习下这个mapXXX辅助函数
// store.js 还是上面那个
// demo1.vue 稍作改变
<template>
<div class="hello">
<p>count1: {{ count }}</p>
<p>count2: {{ $store.state.count }}</p>
<button @click="plus">点击+2</button>
</div>
</template>
<script>
import {mapState} from 'vuex'
export default {
data () {
return {}
},
computed:{
...mapState({
count: 'count'
})
},
methods:{
plus(){
console.log('点击+2');
this.$store.commit('plus', 2);
}
}
}
</script>
这里我先用一下mapState这个辅助函数,因为state是用来保存数据的,在组件中对应的就是computed计算属性了,因此我们在computed中映射一个count变量来接收state中的count变量,当然我们可以在这里给count1的变量任意命名,例如
computed: mapState({ count1: 'count' }),
,这样在用{{ count1 }}
就可以接收到映射过来的state中的公共状态了,同时也会保持实时渲染。<p>count1: {{ count }}</p>
现在的渲染效果与<p>count2: {{ $store.state.count }}</p>
的渲染效果一样了,很神奇吧~~循序渐进,我们来看一下mapGetter的使用
// demo1.vue 稍作改变
<template>
<div class="hello">
<p>count1: {{ count }}</p>
<p>count2: {{ $store.state.count }}</p>
<p>app1: {{ app }}</p>
<p>app2: {{ $store.getters.app }}</p>
<button @click="plus">点击+2</button>
</div>
</template>
<script>
import {mapState} from 'vuex'
export default {
data () {
return {}
},
computed:{
...mapState({
count: 'count'
}),
...mapGetters([
'app'
])
},
methods:{
plus(){
console.log('点击+2');
this.$store.commit('plus', 2);
}
}
}
</script>
- 这样我们的getters就被映射到了demo1.vue组件中,就跟在组件中使用自己的状态(变量)一样方便了
- 这里细心地同学就会发现,store.js中的getters有一个返回的是一个函数,不是数值能不能放到计算属性中呢?试一试吧
<template>
<div class="hello">
<p>count1: {{ count }}</p>
<p>count2: {{ $store.state.count }}</p>
<p>app1: {{ app }}</p>
<p>app2: {{ $store.getters.app }}</p>
<p>add1: {{ add(num) }}</p>
<p>add2: {{ $store.getters.add(num) }}</p>
<button @click="plus">点击+2</button>
</div>
</template>
<script>
import {mapState,mapGetters} from 'vuex'
export default {
data () {
return {
num: 100
}
},
computed:{
...mapState({
count: 'count'
}),
...mapGetters([
'app',
'add'
])
},
created(){
console.log('getters ', this.$store.getters.add(20));
},
methods:{
plus(){
console.log('点击+2');
this.$store.commit('plus', 2);
}
}
}
</script>
运行结果完全没问题,还可以传入参数
mapMutations和mapActions的使用
// demo1.vue
<template>
<div class="hello">
<p>count1: {{ count }}</p>
<p>count2: {{ $store.state.count }}</p>
<p>app1: {{ app }}</p>
<p>app2: {{ $store.getters.app }}</p>
<p>add1: {{ add(num) }}</p>
<p>add2: {{ $store.getters.add(num) }}</p>
<button @click="plus">点击+2</button>
<br><br>
<button @click="getData">读取json</button>
</div>
</template>
<script>
import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'
export default {
data () {
return {
num: 100
}
},
computed:{
...mapState({
count: 'count'
}),
...mapGetters([
'app',
'add'
])
},
methods:{
// ...mapMutations([
// 'plus'
// ]),
...mapMutations({
newNamePlus: 'plus'
}),
...mapActions([
'actionA',
'actionB'
]),
plus(){
console.log('点击+2');
// this.$store.commit('plus', 2);
/**
* 注意!!!严重错误
*
* 因为我们的store.mutations中有个'plus'方法,上面注释掉的mapMutations映射过来的时候,
* 方法名与本组件内的点击事件'plus()'方法同名,在下面直接调用(已被注释掉)会出现死循环,在成内存泄漏,继而使浏览器卡死的问题
*
* 所以这里需要注意,使用上面为注释掉的mapMutations映射方法,
* 如有同名函数方法在映射时一定要给方法重新命名
*/
// this.plus(2);
this.newNamePlus(2); // 实现效果与this.$store.commit('plus', 2);完全相同
},
getData(){
this.actionB();// 作用等同与没有使用mapActions映射时的this.$store.dispatch('actionB');
}
}
}
</script>
- 这里也有个大坑,有的时候 会出现命名重复的问题,不使用mapXXX方法时因为都在各自的文件中被调用,不会出现互相影响的问题,但是映射到同一个组件中时就可能出现上面的问题,造成程序出现内存泄漏的bug
- 至于mapMutations和mapActions的映射一样,都是两种方法:直接映射的话,使用
...mapActions(['方法1','方法2'])
即可,如果映射过来时需要重新命名,那就需要用...mapActions({ newName1: '方法1', newName2: '方法2'})
最后,vuex在很小的项目中是显得有些冗余的,中型项目中,简单的vuex功能就可以很好的实现大多数功能了,大型项目中,必须严格的使用vuex的各部分,做到条理清晰,才能使团队开发高效的进行
- 我的代码仓库地址,欢迎大神拍砖,有问题随时沟通