Vuex
# Vuex
# Vuex 工作原理
# 配置 Vuex 环境
安装 Vuex:
npm install vuex --save
在 src 文件夹中创建 store
文件夹,并在其中创建 index.js
文件,编写如下内容:
"use strict";
import Vuex from "vuex";
import Vue from "vue";
Vue.use(Vuex);
const actions={}
const mutations={}
const state={}
export default new Vuex.Store({
actions,
mutations,
state
})
2
3
4
5
6
7
8
9
10
11
12
注意
Vue.use()
必须要写在创建 store 对象前,如果写在 main.js 中然后引用 index.js,且使用了 Vue-CLI 脚手架,则脚手架会先执行所有的 import 语句,导致导入 index.js 前没有执行 Vue.use()
发生报错,所以要写在 index.js 中。
main.js 引入 index.js:
import Vue from 'vue'
import App from './App.vue'
import store from './store'
Vue.config.productionTip = false;
new Vue({
render: h => h(App),
store
}).$mount('#app')
2
3
4
5
6
7
8
9
# 求和案例
求和子模块:
通过 store
的 dispatch()
方法触发 actions:
<template>
<div>
<h2>当前n为{{n}}</h2>
<select v-model.number="step">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="incrementOdd">当前求和为奇数才加</button>
<button @click="incrementWait">延迟两秒加</button>
</div>
</template>
<script>
export default {
name: "Counter",
data(){
return{
step:1,
}
},
methods:{
increment(){
this.$store.dispatch('add',this.step);
},
decrement(){
this.$store.dispatch('sub',this.step);
},
incrementOdd(){
this.$store.dispatch('addOdd',this.step);
},
incrementWait(){
this.$store.dispatch('addWait',this.step);
}
},
computed:{
n:{
get(){
return this.$store.state.n
}
}
}
}
</script>
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
36
37
38
39
40
41
42
43
44
45
46
47
actions 和 mutations:
const actions={
// context与 store 实例具有相同方法和属性
add(context,data) {
context.commit('ADD',data);
},
sub(context,data) {
context.commit('SUB',data);
},
addOdd(context,data) {
//在actions中再次分发给其他action并接受返回值,返回值是promise类型
context.dispatch('isOdd',context.state.n).then((result)=>{
console.log(result);
if (result){
context.commit('ADD',data);
}
})
},
addWait(context,data) {
setTimeout(()=>{
context.commit('ADD',data);
},2000);
},
isOdd(context,data) {
return data % 2 !== 0;
}
}
const mutations={
ADD(state,data) {
state.n+=data
},
SUB(state,data){
state.n-=data;
}
}
const state={
n:0
}
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
36
37
38
39
# getters
当要获取加工后的 state 数据时使用:
<h2>当前n的算术平方根为{{sqrtN}}</h2>
<script>
export default {
name: "Counter",
computed:{
sqrtN:{
get() {
return this.$store.getters.process;
}
}
}
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
index.js:
const getters={
process(state) {
return Math.sqrt(state.n)
}
}
export default new Vuex.Store({
actions,
mutations,
state,
getters
})
2
3
4
5
6
7
8
9
10
11
getters 定义与计算属性类似,但是能做到全局复用,计算属性只能组件内复用,都通过返回值获取属性值。
# mapState 与 mapGetters
在组件中频繁写 this.$store.state
过于繁琐,使用 mapState
与 mapGetters
可以简化代码。
首先在组件中引入 mapState
和 mapGetters
:
import {mapState,mapGetters} from 'vuex'
在计算属性中使用,前面加三个点表示以对象形式加入:
computed: {
// 对象写法
// ...mapState({n:'n',name:'name',age:'age'})
// ...mapState({n:state => state.n,name:state => state.name,age:state => state.age})
...mapState({
n(state) {
return state.n;
},
name(state) {
return state.name;
},
age(state) {
return state.age*100;
}
}),
// 数组写法
// ...mapState(['n','name','age'])
...mapGetters({sqrtN:'process'})
// ...mapGetters(['process'])
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
mapState
有四种写法:
- 对象写法,对象中是键值对,键为需要的计算属性名,值必须用引号,表示在
state
中对应数据的名字。 - 对象写法,对象中是键值对,键为需要计算的属性名,值为箭头函数,参数为
state
。 - 对象写法,对象中是函数,函数参数为
state
,可以进行复杂处理。 - 数组写法,仅适用于需要的计算属性与
state
中属性重名的情况。
mapGetters
有两种写法:
- 数组写法,仅适用于需要的计算属性与
getters
中函数重名的情况。 - 对象写法,对象中是键值对,键为需要的计算属性,值为
getters
中对应函数的名字。
# mapActions 与 mapMutations
在组件中频繁创建方法调用 actions 或 mutations 非常繁琐,使用 mapActions
和 mapMutations
可以简化代码。
首先在组件中引入 mapActions
和 mapMutations
:
import {mapActions, mapMutations} from 'vuex'
在 methods 中应用:
methods: {
// 调用时传参
...mapMutations({increment:'ADD',decrement:'SUB'}),
// 调用时传参
// ...mapMutations(['ADD','SUB']),
// 调用时要传参数
...mapActions({incrementOdd:'addOdd', incrementWait:'addWait'})
// 调用时要传参数
// ...mapActions(['addWait','addOdd'])
},
2
3
4
5
6
7
8
9
10
11
12
mapActions
有两种写法:
- 对象写法,键为要定义的方法名,值为 actions 中定义的方法。
- 数组写法,仅适用于要定义的方法名和定义在 actions 中的方法同名的情况。
mapMutations
有两种写法:
- 对象写法,键为要定义的方法名,值为 mutations 中已定义的方法名。
- 数组写法,仅适用于要定义的方法名和定义在 mutations 中的方法同名的情况。
Tips
mapActions
和 mapMutations
不论使用哪种写法,都必须在调用时传入参数,否则默认参数是 event。
示例:
<button @click="incrementOdd(step)"></button>
<button @click="incrementWait(step)"></button>
2
# 多组件共享数据
在此前计数器组件基础上,添加人员列表组件,完成人员列表组件能看到计数器当前值,计数器组件能看到人员列表长度。
在 index.js 中给 actions 添加方法:
addPerson(context,data) {
if (data.trim()===''){
alert('请输入内容')
}else {
context.commit('ADD_PERSON',data)
}
}
2
3
4
5
6
7
在mutations中添加:
ADD_PERSON(state,data){
state.people.unshift(data)
alert('陈坤')
}
2
3
4
为 state 添加一个人员数组:
const state={
n:0,
name:'PPG',
age:21,
people:[]
}
2
3
4
5
6
在计数器组件模板中添加:
<h4>当前人员列表中人数为:{{number}}</h4>
其中 number
是计算属性,定义如下:
computed: {
...mapState({
n(state) {
return state.n;
},
name(state) {
return state.name;
},
age(state) {
return state.age;
},
number(state) {
return state.people.length;
}
}),
2
3
4
5
6
7
8
9
10
11
12
13
14
15
创建人员列表组件:
<template>
<div>
<div>
<input v-model="temp" type="text" placeholder="输入姓名,回车确认" @keydown.enter="addPerson(temp)">
</div>
<ul>
<li v-for="(item,index) in people" :key="index">
{{item}}
</li>
</ul>
<div>
计数器组件的当前值为:{{n}}
</div>
</div>
</template>
<script>
import {mapActions,mapState} from 'vuex'
export default {
name: "People",
data(){
return{
temp:''
}
},
methods:{
...mapActions(['addPerson'])
},
computed:{
...mapState(['people','n'])
},
//通过监视people数组变化清空输入框
watch:{
people:{
deep:true,
handler(){
this.temp=''
}
}
}
}
</script>
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
36
37
38
39
40
41
42
# 模块化
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割。
将上一个部分中人员列表和计数器的 vuex 拆分,可以写在 index.js 中,也可以每个模块拆分出一个文件然后 index 中进行引用。
people.js:
"use strict";
export default {
namespaced: true,
actions: {
addPerson(context, data) {
if (data.trim() === '') {
alert('请输入内容')
} else {
context.commit('ADD_PERSON', data)
}
}
},
mutations: {
ADD_PERSON(state, data) {
state.people.unshift(data)
alert('陈坤')
}
},
getters: {},
state: {
people: []
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
counter.js:
"use strict";
export default {
namespaced:true,
actions: {
addOdd(context, data) {
context.dispatch('isOdd', context.state.n).then((result) => {
if (result) {
context.commit('ADD', data);
}
})
},
addWait(context, data) {
setTimeout(() => {
context.commit('ADD', data);
}, 2000);
},
isOdd(context, data) {
return data % 2 !== 0;
},
},
mutations: {
ADD(state, data) {
state.n += data
},
SUB(state, data) {
state.n -= data;
},
},
getters: {
process(state) {
return Math.sqrt(state.n)
}
},
state: {
n: 0,
name: 'PPG',
age: 21,
}
};
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
36
37
38
39
index.js:
"use strict";
import Vuex from "vuex";
import Vue from "vue";
import counterOptions from "@/store/counter";
import peopleOptions from "@/store/people";
Vue.use(Vuex);
export default new Vuex.Store({
modules:{
peopleOptions,
counterOptions
}
})
2
3
4
5
6
7
8
9
10
11
12
13
在分模块中添加 namespaced
属性开启命名空间,如果不写,默认为 false,每个模块中的 actions 和 mutations 为全局可用,state 不是全局可用,要访问可以使用模块名前缀。
counter.vue:
<script>
import {mapState, mapGetters} from 'vuex'
import {mapActions, mapMutations} from 'vuex'
export default {
name: "Counter",
data() {
return {
step: 1,
}
},
methods: {
...mapMutations('counterOptions',{increment:'ADD',decrement:'SUB'}),
...mapActions('counterOptions',{incrementOdd:'addOdd', incrementWait:'addWait'})
},
computed: {
...mapState('counterOptions',{
n(state) {
return state.n;
},
name(state) {
return state.name;
},
age(state) {
return state.age;
},
}),
...mapState('peopleOptions',{
number(state) {
return state.people.length;
}
}),
...mapGetters('counterOptions',{sqrtN: 'process'})
}
}
</script>
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
36
37
38
people.vue:
<script>
import {mapActions,mapState} from 'vuex'
export default {
name: "People",
data(){
return{
temp:''
}
},
methods:{
...mapActions('peopleOptions',['addPerson'])
},
computed:{
...mapState('peopleOptions',['people']),
...mapState('counterOptions',['n']),
},
watch:{
people:{
deep:true,
handler(){
this.temp=''
}
}
}
}
</script>
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
在所有的 map 辅助函数中第一个参数传入在 index.js 中注册的模块名即可。
如果不使用辅助函数,要注意访问的路径为命名空间/getters 方法名
。
例如通过如下方式在 counter.vue 中访问。
//返回值就是getters对应函数的结果
this.$store.getters["counterOptions/process"]
2
对于模块内部的 action,局部状态通过 context.state
暴露出来,根节点状态则为 context.rootState
。
对于模块内部的 getter,根节点状态会作为第三个参数暴露出来。