Vuex笔记

我这篇文章也放在我的GitHub上,里面还有相应的代码。建议移步GitHub阅读本文章。

本文代码

  • 前导~state部分的全部代码: 点击
  • 应用(一)代码: 点击
  • getters 应用(二)代码: 点击
  • mutations 应用(三)代码:点击
  • actions 应用(四)代码:点击



前导

结合代码案例学习,本文的知识点会依附在这个代码里讲解,下面先来看一下这个代码的介绍,主要就是一个父组件Index.vue,两个子组件AddStudent.vue和StudentList.vue,之间有组件通讯。

1562291573640


父组件 Index.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<template>
<div class="index">
rock!我是index.vue
<add-student @add="add"></add-student>
<student-list :student="student"></student-list>
</div>
</template>

<script>
import AddStudent from "../components/AddStudent";
import StudentList from "../components/StudentList";
export default {
data() {
return {
student: []
};
},
methods: {
add(name) {
this.student.push(name);
console.log(this.student);
}
},
components: {
AddStudent,
StudentList
}
};
</script>

<style scoped>
.index {
background-color: rgb(72, 138, 182);
}
</style>


子组件 AddStudent.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<template>
<div class="AddStudent">
<h4>我是addStudent</h4>
<span>添加学生</span>
<input id="input" type="text" v-model="name" />
<button @click="add">添加</button>
</div>
</template>

<script>
export default {
data() {
return {
name: ""
};
},
methods: {
add() {
this.$emit("add", this.name);
}
}
};
</script>

<style scoped>
.AddStudent {
border: 2px solid green;
background-color: green;
}
</style>


子组件 Student List.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
<div class="StudentList">
<h4>我是StudentList</h4>
<ul>
<li v-for="(item, index) in student" :key="index+item">{{item}}</li>
</ul>
</div>
</template>

<script>
export default {
data() {
return {};
},
props: ["student"]
};
</script>

<style scoped>
.StudentList {
border: 2px solid red;
background-color: red;
}
</style>



state

store.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
state: {
name: 'dunteng',
age: 20,
look: 'handsome'
},
mutations: {

},
actions: {

}
})


$store.state.xxx

vuex 里的 state 相当于一个公共数据池。

在 AddStudent.vue 中通过如下方式可以获取到:

  1. 使用 $store.state.name 获取到

    1562292413010

    然后我修改一下add方法使得点击button按钮后修改 $store.state.name ,如下:

    1
    2
    3
    4
    add() {
    // this.$emit("add", this.name);
    this.$store.state.name += " lin";
    }

    但是显示的部分并没有成功修改只有才有被修改,这是因为storeName是在data中被定义的,之后$store.state.name和它是没关系的,影响不到。所以应该用computed来获取 state 的数据。

  2. computed 获取 this.$store.state.name

    1562292792863

    这样,只要 state 里的数据发生改变,通过 computed 获取到的就能相应其改变。


mapState

背景

像上述那样拿到 state 中的 name ,继续获取到 state 中的其他数据:

1562293651685

如果同时另一个组件也要同样的这些数据,那么就要在 StudentList 组件中重复上述的操作,这样是可行的,但是如果还有七八九十个组件也要这些数据,那么这样的操作很繁琐,冗余很大。

这就需要用到 mapState方法 了。这个方法的返回是一个对象,对象的每个key对应的value都是一个函数,有一点点像下面这个:

1562294928788


用法

先引入才能使用。

1
import {mapState} from 'vuex'

然后结合 computed ,将 state 中的数据作为参数传入,

1
computed: mapState(["name", "age", "look"]),

而在当前组件中获得的数据名称和 state 中的一样。

1562295563445

但是结果只显示了 age 和 look ,没有显示 name ,因为在 data 中定义了一个 name 属性,而 data 的优先级比 computed 高,所以无法正常获取到。

所以需要我们在使用 mapState 的时候重命名,如下:

1
2
3
4
5
computed: mapState({
mapStateName: state => state.name,
mapStateAge: state => state.age,
mapStateOther: () => "你好吗" //我们甚至可以定义与state无关的变量
}),

但是除了 mapState ,我们在 computed 中也需要定义其他的计算属性,咋整?

像下面这样写是会报错的!

1562297668974

上面说了 mapState 返回的是一个对象,所以我们可以使用扩展运算符,如下:

1
2
3
4
5
6
7
8
9
10
computed: {
...mapState({
mapStateName: state => state.name,
mapStateAge: state => state.age,
mapStateOther: () => "你好吗"
}),
a(){
retrun this.$store.state.age+666
}
},


应用(一)

【代码】

原案例中的两个子组件之间的传值是通过父组件来实现的,下面我们利用 vuex 的 state 来存储他们的公共资源。

1562307401416


父组件 Index.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<template>
<div class="index">
rock!我是index.vue
<add-student></add-student>
<student-list></student-list>
</div>
</template>

<script>
import AddStudent from "../components/AddStudent";
import StudentList from "../components/StudentList";
export default {
data() {
return {};
},
methods: {},
components: {
AddStudent,
StudentList
}
};
</script>

<style scoped>
.index {
background-color: rgb(72, 138, 182);
}
</style>


子组件 AddStudent.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<template>
<div class="AddStudent">
<h4>我是addStudent</h4>
<span>添加学生</span>
<input id="input" type="text" v-model="name" />
<button @click="add">添加</button>
</div>
</template>

<script>
import { mapState } from "vuex";
export default {
data() {
return {
name: ""
};
},
methods: {
add() {
// this.$emit("add", this.name);
this.$store.state.studentlist.push(this.name);
console.log(this.$store.state.studentlist);
}
}
};
</script>

<style scoped>
.AddStudent {
border: 2px solid green;
background-color: green;
}
</style>


子组件 StudentList.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<template>
<div class="StudentList">
<h4>我是StudentList</h4>
<ul>
<li v-for="(item, index) in studentlist" :key="index+item">{{item}}</li>
</ul>
</div>
</template>

<script>
import { mapState } from "vuex";

export default {
data() {
return {};
},
computed: {
...mapState(["studentlist"]) //记得加引号。。。
}
};
</script>

<style scoped>
.StudentList {
border: 2px solid red;
background-color: red;
}
</style>



getters

应用(二)

在应用(一)的基础上,要求输出:

索引为0的: **姓名

索引为1和2的:姓名**

索引大于2的:++姓名++

效果图:

1562335007899


不用getters的方案

在 StudentList.vue中的 computed 中进行数据处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
computed: {
// ...mapState(["studentlist"]) //记得加引号。。。
studentlist() {
var list = [];
this.$store.state.studentlist.forEach((element, index) => {
if (index == 0) {
element = "**" + element;
list.push(element);
} else if (index == 1 || index == 2) {
element = element + "**";
list.push(element);
} else {
element = "++" + element + "++";
list.push(element);
}
});
return list;
}
}


使用getters的方案

现在 store.js 中写 getters 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
getters: {
//相当于计算属性
newstudentlist(state) {
return state.studentlist.map((ele, index) => {
if (index == 0) {
return '**' + ele
} else if (index < 3) {
return ele + '**'
} else {
return '++' + ele + '++'
}
})
}
},

在 StudentList 中直接使用 newstudentlist:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<div class="StudentList">
<h4>我是StudentList</h4>
<ul>
<li v-for="(item, index) in newstudentlist" :key="index+item">{{item}}</li>
</ul>
</div>
</template>

<script>
import { mapState } from "vuex";

export default {
data() {
return {};
},
computed: {
newstudentlist() {
return this.$store.getters.newstudentlist;
}
}
};
</script>


mapGetters

和 mapState 类似的便捷方法

StudentList 中先引入 mapGetters :

1
2
3
4
5
6
7
8
import { mapState, mapGetters } from "vuex";
。。。
computed: {
// newstudentlist() {
// return this.$store.getters.newstudentlist;
// }
...mapGetters(["newstudentlist"])
}

也可以使用对象的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
import { mapState, mapGetters } from "vuex";
。。。
computed: {
// newstudentlist() {
// return this.$store.getters.newstudentlist;
// }

//...mapGetters(["newstudentlist"])

...mapGetters({
newstudentlist: "newstudentlist"
})
}


补充一点:getters自身可以作为参数传入getters属性的方法中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
getters: {
//相当于计算属性
newstudentlist(state, getters) {//传入了getters作为参数
return state.studentlist.map((ele, index) => {
if (index == 0) {
return '**' + ele + getters.aaa//使用了getters中的aaa
} else if (index < 3) {
return ele + '**'
} else {
return '++' + ele + '++'
}
})
},
aaa() {
return 666
}
},



mutations

关于严格模式

上述应用程序代码写的是不规范的,当我们开启严格模式时就会报警告了。

1562337035921

1562337084677

报错信息提示不要在 mutation 处理程序之外对vuex存储状态state进行更改。而我们恰恰就在 Addstudent.vue 的 add 方法中对 $store.state.studentlist 进行了更改:

1562337274020

【记住:凡是要修改 state 的都要通过 mutation 来修改。 】


应用(三)

【这部分代码】

先在 store.js 中编写 mutations :

1
2
3
4
5
mutations: {
changeStudent(state, param) {
state.studentlist.push(param)
}
},

第二个参数接受传入的外部参数。

在 AddStudent.vue 中:

1
2
3
4
5
methods: {
add() {
this.$store.commit("changeStudent", this.name);
}
}

注意写法,对象是 $store ,使用了 commit

dfagaha


在 mutation 属性中智能传递两个参数,如果我们想传递多个参数,可以把参数写进一个对象,然后这个对象作为参数写入 mutation 中。

1
2
3
4
5
mutations: {
changeStudent(state, { name, number }) {//这里注意对象参数里面的元素命名形参和实参必须保持一致
state.studentlist.push(name + number)
}
},

在 AddStudent.vue 中:

1
2
3
4
5
methods: {
add() {
this.$store.commit("changeStudent", { name: this.name, number: 12 });
}
}

【注意:注意对象参数里面的元素命名形参和实参必须保持一致】


mapMutations

和 mapGetters 一样, mutations 也有便捷操作。

在 AddStudent.vue 中,先引入 mapMutations

然后在 methods 定义:

1
2
3
4
5
6
7
8
9
10
11
methods: {
add() {
// this.$emit("add", this.name);
// this.$store.state.studentlist.push(this.name);
// console.log(this.$store.state.studentlist);

// this.$store.commit("changeStudent", { name: this.name, number: 12 });//mutations

},
...mapMutations(['changeStudent']) //mutations里的方法叫什么这里就填什么
}

这样在 AddStudent.vue 中就有了 changeStudent 这一方法,直接使用即可。

1
2
3
4
5
6
7
8
9
<template>
<div class="AddStudent">
<h4>我是addStudent</h4>
<span>添加学生</span>
<input id="input" type="text" v-model="name" />
<!-- <button @click="add">添加</button> -->
<button @click="changeStudent({name,number:18})">添加</button>
</div>
</template>

如果想要重命名,如下操作:

1
2
3
4
5
6
7
8
9
10
11
12
methods: {
add() {
// this.$emit("add", this.name);
// this.$store.state.studentlist.push(this.name);
// console.log(this.$store.state.studentlist);
// this.$store.commit("changeStudent", { name: this.name, number: 12 });//mutations
},
// ...mapMutations(['changeStudent']) //mutations里的方法叫什么这里就填什么
...mapMutations({
newChangeStudent: "changeStudent" //重命名
})
}

然后在标签里触发这个 newChangeStudent 即可。



actions

异步操作下的情况

我们尝试在 mutations 中模拟一下异步操作,实现点击后过了1秒再执行 mutations 中的动作:

1
2
3
4
5
6
7
mutations: {
changeStudent(state, { name, number }) {//这里注意对象参数里面的元素命名形参和实参必须保持一致
setTimeout(() => {
state.studentlist.push(name + number)
}, 1000);
}
},

然后发现控制台报错:

1562376986538

还是之前见过的报错信息,提示不要在 mutation 处理程序之外对vuex存储状态state进行更改。而我们在 mutations 里面的 setTimeout 下对 state 进行了修改,作用域不在 mutations 下,因此报错。

还有一点非常糟糕的是:如果我们这样写了,在点击按钮的时候用极端的时间点击了很多次,当后来我们想要调试的时候,回不到正确的 state 。我们通过 vue Devtool 调试,当我想要看第三次点击时的 state 时,结果左边跳到了初试的 state 状态,因为我的第三次点击时,第一次点击还不到1秒,state 还尚未被修改!如下图所示

faga

于是我们可以使用 vuex 中的异步操作 actions

mutations 是用来执行修改 state 中数据的操作,actions 也是,但是特别的是,actions 专门用来进行异步的修改操作。

应用(四)

在 store.js 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
mutations: {
changeStudent(state, { name, number }) {//这里注意对象参数里面的元素命名形参和实参必须保持一致
state.studentlist.push(name + number)
}
},
actions: {
changeStudent(ctx, { name, number }) {
//这个参数ctx是上下文对象,相当于当前的$store
setTimeout(() => {
ctx.commit('changeStudent', { name, number })//这里调用了mutations 中的changeStudent方法
}, 1000);
}
}

在 AddStudent.vue 中,通过 dispatch 来触发 actions :

1
2
3
4
5
6
7
8
9
10
methods: {
add() {
// this.$emit("add", this.name);
// this.$store.state.studentlist.push(this.name);
// console.log(this.$store.state.studentlist);

// this.$store.commit("changeStudent", { name: this.name, number: 12 });//mutations
this.$store.dispatch('changeStudent',{name:this.name,number:12})
}
}

dfagaga

在这个例子中,使用了 actions ,每次点击按钮的时候它都不是立即就执行 changeStudent 方法,而是每次都等候1秒再执行了,这就和用 mutations 内部 setTimeout 不一样,不是楞头地立即执行,导致 state 混乱。

mapActions

同样的,actions 也有便捷操作。mapActions 返回的也是一个对象,里面都是函数。

在 AddStudent.vue 的 methods 中创建 mapActions:

1
2
3
methods: {
...mapActions(["changeStudent"])//actions里面的方法叫什么这里就写什么
}

这样 AddStudent.vue 中就有了 changeStudent 这样一个方法,可以像普通方法那样使用:

1
2
3
4
5
6
7
8
<template>
<div class="AddStudent">
<h4>我是addStudent</h4>
<span>添加学生</span>
<input id="input" type="text" v-model="name" />
<button @click="changeStudent({name,number:6})">添加</button>
</div>
</template>

如果要重命名,如下操作:

1
2
3
4
5
methods: {
...mapActions({
newChangeStudent: "changeStudent"
})
}

然后在标签中触发 newChangeStudent 方法即可。



Modules

未完待续