# Vue2教程 - 16 Vuex
Vuex 是 Vue 配套的公共数据管理工具,它可以把一些组件共享的数据,保存到 Vuex 中,方便整个程序中的任何组件直接获取或修改这些公共数据。
之前学了父子组件之间传递数据,通过属性
及回调函数
的形式来传递。但是如果涉及到多级组件嵌套,各个组件之间传递数据会非常麻烦。尤其是遇到没有父子关系的组件,在其间传递数据会更麻烦。
Vuex 就是为了保存组件之间的共享数据,如果组件之间有要共享的数据,可以把数据保存到 Vuex 中,Vuex就是提供一个全局的共享数据存储区域,相当于一个数据仓库,各个组件都可以从中读取数据。
# 16.1 安装Vuex
# 1 安装vuex
# npm方式安装
npm install vuex@3 --save
# yarn方式安装
yarn add vuex@3
2
3
4
5
Vue2 中只能使用 vuex3版本。
# 2 创建数据存储对象
如果在一开始新建项目的时候,就勾选了Vuex,那么会在项目的 src
下创建一个 store
目录,并包含了一个 index.js
文件。
所以如果你创建项目的时候,没有勾选,那么我们也按照这样的方式来操作。首先在 src
下创建一个 store
目录,然后在其下创建一个 index.js
文件,内容如下:
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {},
getters: {},
mutations: {},
actions: {},
modules: {},
});
2
3
4
5
6
7
8
9
10
11
12
在上面的代码中,是时候用Vuex创建了一个数据存储对象,在其中有一些属性选项,这个后面再解释。
其中:
state
属性是用来存储的公共数据;mutations
属性是用来操作state
中的数据的方法,只能通过mutations
中提供的方法来操作state
中的数据;
# 3 挂载数据存储对象
在项目的 main.js
中引入 Vuex
的数据存储对象,并挂载到Vue实例上。
import Vue from 'vue'
import App from './App.vue'
// 引入路由
import router from "./router";
// 1.引入Vuex
import store from "./store";
Vue.config.productionTip = false
new Vue({
router,
store, // 2.挂载
render: h => h(App),
}).$mount('#app')
2
3
4
5
6
7
8
9
10
11
12
13
14
我们已经将 store
挂载到了 Vue 实例上,那么任何组件对象都可以通过 $store.state.属性
的方式获取到属性的值。
修改 store 中的数据千万不要这么用 this.$store.state.属性=值
的方式来修改,不符合 Vuex 的设计理念。因为直接操作 state
中的数据,因为万一导致了数据的紊乱,不能快速定位到错误的原因,因为每个组件都可能有操作数据;所以如果要操作 store
中的 state
值,只能通过调用 mutations
提供的方法,才能操作对应的数据。
下面演示一下 Vuex 的使用。
# 16.2 使用Vuex
下面通过一个样例来学习 Vuex 的运用。
实现一个功能,在一个组件中有两个按钮:增减和减少,当点击增减和减少按钮的时候,修改 store
中的值,在另一个组件中获取到store
中的数据并显示出来。这两个组件是平级的关系,没有父子关系,在之前是很难做到的。
# 1 编写store/index.js
- 在
store
对象的state
属性中添加一个count
属性,用来存储显示的值,当增减和减少的时候,就操作这个count
属性; - 在
store
的mutations
定义两个方法,用来增减和减少count
的值,state
中的数据只能通过mutations
中的方法来操作;
store/index.js
:
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
// 定义数据
count: 0
},
//mutations中的方法的第一个参数就是state对象
mutations: {
// 增加count
incrementCount(state) {
state.count++
},
// 减少count,接收一个减数
decrementCount(state, subtrahend) {
state.count -= subtrahend;
}
},
getters: {},
actions: {},
modules: {},
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
mutations
中的方法最多只能有两个参数,第一个参数是固定的,就是state
对象,第二个参数我们在调用的时候可以传值过来,如果想传递多个值,只能通过对象的形式来传值,在对象中定义多个属性。怎么调用
mutations
中的方法,后面会讲到;
# 2 定义两个组件
编写 CounterCom.vue
组件,在组件中只是通过 $store.state.count
来获取 store
中 state
中的 count
数据,还使用了一个 input
绑定了 count
的数据,这样 state
中的数据发生了变化就会显示在 CounterCom.vue
组件上。
代码如下:
<template>
<div>
<!-- 通过$store.state.属性获取数据 -->
<h3>{{ $store.state.count }}</h3>
<!-- 和store中的值实现绑定 -->
<input type="text" v-model="$store.state.count">
</div>
</template>
<script>
</script>
2
3
4
5
6
7
8
9
10
11
12
注意:form元素不能和state中的属性实现双向绑定,如果要实现双向绑定,需要使用组件和组件自己data中的数据实现双向绑定,同时监听组件中数据的变化,然后修改state中的数据。上面的不规范。
创建 OperateCom.vue
文件,主要是用来操作 Vuex 中的数据。
编写代码如下:
<template>
<div>
<button @click="increment">增加</button>
<button @click="decrement">减少</button>
</div>
</template>
<script>
export default {
methods: {
increment() {
// 千万不要使用this.$store.state.count++;,不符合 vuex 的设计理念
//调用store中的方法
this.$store.commit("incrementCount");
},
decrement() {
this.$store.commit("decrementCount", 2);
}
}
};
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
在组件中定义了增减和减少的按钮,当点击按钮的时候,调用组件中定义的方法,在定义的方法中操作 store
中的数据,通过this.$store.commit
来调用 store
中 mutations
中定义的方法。
# 3 使用两个组件
我就直接在 App.Vue 中演示了,在其中引入并使用这两个组件;
<template>
<div>
<CounterCom></CounterCom>
<br>
<OperateCom></OperateCom>
</div>
</template>
<script>
import CounterCom from '@/components/CounterCom.vue';
import OperateCom from '@/components/OperateCom.vue';
export default {
data: function() {
return {
}
},
components: {
CounterCom,
OperateCom
}
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
运行项目,当点击OperateCom组件中的增加和减少的按钮的时候,CounterCom中显示的数值会自动变化。
# 16.3 getters属性
当从 Vuex 中获取数据的时候,想要做一些转换,可以使用 getters
,例如在之前的 state
中存储的 count
,如果想要获取 count
的时候,获取到的值前面加上 总计:
那么就可以使用 getters
。
在 store
中定义 getters
属性,在属性中定义对应的属性及对应的函数。
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
// 定义数据
count: 0
},
//mutations中的方法的第一个参数就是state对象
mutations: {
// 增加count
incrementCount(state) {
state.count++
},
// 减少count,接收一个减数
decrementCount(state, subtrahend) {
state.count -= subtrahend;
}
},
getters: {
optCount: function (state) {
return '总计:' + state.count;
}
},
actions: {},
modules: {},
});
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
通过上面定义的 getter
,在获取的时候,就可以通过 $store.getters.optCount
来获取转换后的 count
的值了。
在 CounterCom
组件中,可以获取转换后的 count
值,当然也可以直接获取 count
的值
<template>
<div>
<!-- 获取转换后的count的值 -->
<h3>{{ $store.getters.optCount }}</h3>
<br>
<!-- 直接获取count的值 -->
<input type="text" v-model="$store.state.count">
</div>
</template>
2
3
4
5
6
7
8
9
显示效果:
总结:
state
中的数据,不能直接修改,如果想要修改,必须通过mutations
;- 如果组件想要直接 从
state
上获取数据: 需要this.$store.state.属性
; - 如果组件想要修改数据,必须使用
mutations
提供的方法,需要通过this.$store.commit('方法的名称',唯一的一个参数);
- 如果需要对
store
中state
上的数据一层包装,然后对外提供,那么推荐使用getters
, 如果需要使用getters
,则用this.$store.getters.函数;
# 16.4 actions
类似于 mutation,不同在于 Action 提交的是 mutation,而不是直接修改 state 中的数据。另外 mutations 只能进行同步操作,但 actions 可以包含异步逻辑。
举个栗子:
# 1 定义actions属性
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
// 定义数据
count: 0
},
//mutations中的方法的第一个参数就是state对象
mutations: {
// 增加count
incrementCount(state) {
state.count++
},
// 减少count,接收一个减数
decrementCount(state, subtrahend) {
state.count -= subtrahend;
}
},
getters: {
optCount: function (state) {
return '总计:' + state.count;
}
},
actions: { // ----定义action
incrementCountAction(context) {
// 调用的是mutations
context.commit('incrementCount');
},
decrementCountAction(context, value) {
setTimeout(() => {
// 调用的是mutations
context.commit('decrementCount', value);
}, 1000);
}
},
modules: {},
});
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
# 2 在组件中调用 Actions
在组件中,你可以通过 this.$store.dispatch
来调用 actions
。
<template>
<div>
<button @click="increment">增加</button>
<button @click="decrement">减少</button>
</div>
</template>
<script>
export default {
methods: {
increment() {
//调用store中的action
this.$store.dispatch("incrementCountAction");
},
decrement() {
//调用store中的action
this.$store.dispatch("decrementCountAction", 2);
}
}
};
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
actions
可以处理异步操作,例如从服务器获取数据。也可以在一个 action
中调度多个 mutations
,主要用来处理更复杂的逻辑。
# 16.5 modules属性
在 Vuex 中,modules 允许你将 store 分割成多个模块,每个模块拥有自己的 state
、mutations
、actions
、getters
,甚至是嵌套子模块。这种分割方式有助于将大型应用的 store 组织得更加合理和清晰。
举个栗子:
假设我们有一个项目,其中包含两个模块:user
和goods
。下面介绍一下如何在 Vuex 中使用这两个模块。
# 1 定义模块
为 user
和 goods
模块创建单独的 js 文件,也在 src/store
文件夹下创建。
user.js
// user.js
export default {
namespaced: true, // 需要开启命名空间
state: {
userInfo: {
name: "逗比",
age: "13"
}
},
mutations: {
setUserInfo(state, userInfo) {
state.userInfo = userInfo;
}
},
actions: {
updateUserInfo({ commit }, userInfo) {
commit('setUserInfo', userInfo);
}
},
getters: {
username(state) {
return 'Hello, ' + state.userInfo.name;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
goods.js
// goods.js
export default {
namespaced: true, // 需要开启命名空间
state: {
goodsList: [
{ id: 1, name: "旺旺大礼包", price: 28.9 },
// ...其他商品
]
},
mutations: {
// ...商品相关的mutations
},
actions: {
// ...商品相关的actions
},
getters: {
// ...商品相关的getters
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 2 注册模块
在 src/store
目录下的 index.js
文件中注册这些模块。
// index.js
import Vue from 'vue';
import Vuex from 'vuex';
// 引入两个模块
import user from './user';
import goods from './goods';
Vue.use(Vuex);
export default new Vuex.Store({
modules: {
user,
goods
}
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 3 在组件中使用模块
在组件中,可以通过:
- 通过
this.$store.state.模块名.状态名
访问状态数据; - 通过
this.$store.commit('模块名/mutation名', payload)
调用mutations
提供的方法; - 通过
this.$store.dispatch('模块名/action名', payload)
调用action
中的方法; - 通过
this.$store.getters['模块名/getter名']
来获取getter。
例如,在组件中访问 user
模块的 状态
、 getter
、action
、mutation
:
<template>
<div>
<!-- 获取state 中的数据 -->
<div>{{ this.$store.state.user.userInfo.name }}</div>
<!-- 调用getter -->
<div>{{ this.$store.getters['user/username'] }}</div>
<button @click="testAction">测试Action</button>
<button @click="testMutation">测试Mutation</button>
</div>
</template>
<script>
export default {
methods: {
testAction: function () {
const newUserInfo = { name: '牛比', age: 30 };
// 提交user模块的setUserInfo mutation
this.$store.commit('user/setUserInfo', newUserInfo);
},
testMutation: function () {
const newUserInfo = { name: '二比', age: 25 };
// 分发user模块的updateUserInfo action
this.$store.dispatch('user/updateUserInfo', newUserInfo);
}
},
}
</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
# 16.6 简化调用Vuex
在 Vuex 中,mapState、mapGetters、mapActions 和 mapMutations 是四个辅助函数,可以帮助我们在 Vue 组件中更简洁地使用 store 的 state、getters、actions 和 mutations。这些辅助函数允许我们将 Vuex store 中的数据和方法映射到组件的计算属性和方法中。
mapState
用于将 store 中的 state 映射到组件的计算属性(computed)中。mapGetters
用于将 store 中的 getters 映射到组件的计算属性中。mapMutations
用于将 store 中的 mutations 映射到组件的 methods 中。mapActions
用于将 store 中的 actions 映射到组件的 methods 中。
下面针对 单模块 和 多模块分别介绍一下。
# 1 单模块
首先介绍一下,Vuex 只有一个模块的情况。
首先 Vuex 配置如下:
// index.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
// 定义数据
count: 0
},
//mutations中的方法的第一个参数就是state对象
mutations: {
// 增加count
incrementCount(state) {
state.count++
},
// 减少count,接收一个减数
decrementCount(state, subtrahend) {
state.count -= subtrahend;
}
},
getters: {
optCount: function (state) {
return '总计:' + state.count;
}
},
actions: { // ----定义action
incrementCountAction(context) {
// 调用的是mutations
context.commit('incrementCount');
},
decrementCountAction(context, value) {
setTimeout(() => {
// 调用的是mutations
context.commit('decrementCount', value);
}, 1000);
}
},
modules: {},
});
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
下面通过 mapState
、mapGetters
、 mapMutations
、mapActions
简化 Vuex 的调用。
在组件中使用如下:
<template>
<div>
<!-- 直接使用count -->
<div>{{ count }}</div>
<!-- 直接使用getter -->
<div>{{ optCount }}</div>
<!-- 直接使用mutations -->
<button @click="incrementCount">加(mutation)</button>
<button @click="decrementCount(2)">减(mutation)</button>
<!-- 直接使用actions -->
<button @click="incrementCountAction">加(action)</button>
<button @click="decrementCountAction(2)">减(action)</button>
<button @click="call">调用方法</button>
</div>
</template>
<script>
import { mapState, mapGetters, mapActions, mapMutations } from 'vuex';
export default {
computed: {
...mapState(['count']), // 映射state
...mapGetters(['optCount']) // 映射getters
},
methods: {
...mapMutations(['incrementCount', 'decrementCount']), // 映射mutations
...mapActions(['incrementCountAction', 'decrementCountAction']), // 映射actions
call: function() {
// 使用this直接可以调用
this.incrementCount();
}
},
}
</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
# 2 多模块
如果 Vuex 分为多个模块。
还是沿用上面的 user 和 goods 模块。
src/user.js
// user.js
export default {
namespaced: true, // 开启命名空间
state: {
userInfo: {
name: "逗比",
age: "13"
}
},
mutations: {
setUserInfo(state, userInfo) {
state.userInfo = userInfo;
}
},
actions: {
updateUserInfo({ commit }, userInfo) {
commit('setUserInfo', userInfo);
}
},
getters: {
username(state) {
return 'Hello, ' + state.userInfo.name;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
src/goods.js
// goods.js
export default {
namespaced: true, // 开启命名空间
state: {
goodsList: [
{ id: 1, name: "旺旺大礼包", price: 28.9 },
// ...其他商品
]
},
mutations: {
// ...商品相关的mutations
},
actions: {
// ...商品相关的actions
},
getters: {
// ...商品相关的getters
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
待会只演示调用 user 模块就可以了,调用 goods 模块也是一样的。
src/index.js
// index.js
import Vue from 'vue';
import Vuex from 'vuex';
// 引入两个模块
import user from './user';
import goods from './goods';
Vue.use(Vuex);
export default new Vuex.Store({
modules: {
user,
goods
}
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
在组件中使用如下:
映射的时,需要指定模块的名称。
<template>
<div>
<!-- 直接使用 -->
<div>{{ userInfo.name }} - {{ userInfo.age }}</div>
<!-- 直接使用getter -->
<div>{{ username }} - {{ userAge }}</div>
<div>{{ goodsCount }} - {{ getCount }}</div>
<!-- 直接使用mutations -->
<button @click="setUserInfo({name: 'niubi', age: 14})">user(mutation)</button>
<button @click="setGoodsCount(20)">goods(mutation)</button>
<!-- 直接使用actions -->
<button @click="updateUserInfo({name: 'niubi', age: 13})">user(action)</button>
<button @click="updateGoodsCount(2)">goods(action)</button>
<button @click="call">调用方法</button>
</div>
</template>
<script>
import { mapState, mapGetters, mapActions, mapMutations } from 'vuex';
export default {
computed: {
...mapState('user', ['userInfo']), // 映射user模块的state中的数据
...mapState('goods', ['goodsCount']), // 映射goods模块的state中的数据
...mapGetters('user', ['username', 'userAge']), // 映射getters
...mapGetters('goods', ['getCount']),
},
methods: {
...mapMutations('user', ['setUserInfo']), // 映射user模块的mutations
...mapMutations('goods', ['setGoodsCount']),
...mapActions('user', ['updateUserInfo']), // 映射user模块的actions
...mapActions('goods', ['updateGoodsCount']),
call() {
this.setUserInfo({name: 'erbi', age: 3}); // 通过this调用
}
}
}
</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
如果多个模块中的 state
、getters
、 mutations
、actions
存在重名呢?
//----state重名
...mapState({
// 使用不同的名称来避免冲突
userStatus: state => state.user.status,
goodsStatus: state => state.goods.status,
}),
//----getters重名
...mapGetters('user', {
userStatusGetter: 'status' // 对应 user 模块的 status
}),
...mapGetters('goods', {
goodsStatusGetter: 'status'
})
//----mutations重名
...mapMutations('user', {
incrementAge: 'increment' // 对应 user 模块的 increment
}),
...mapMutations('goods', {
incrementCount: 'increment' // 对应 counter 模块的 increment
}),
//----actions重名
...mapActions('user', {
incrementCounterAsync: 'incrementAsync' // 对应 user 模块的 incrementAsync
}),
...mapActions('goods', {
incrementAgeAsync: 'incrementAgeAsync'
})
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