Appearance
Vue2教程 - 16 Vuex
Vuex 是 Vue 配套的公共数据管理工具,它可以把一些组件共享的数据,保存到 Vuex 中,方便整个程序中的任何组件直接获取或修改这些公共数据。
之前学了父子组件之间传递数据,通过属性及回调函数的形式来传递。但是如果涉及到多级组件嵌套,各个组件之间传递数据会非常麻烦。尤其是遇到没有父子关系的组件,在其间传递数据会更麻烦。
Vuex 就是为了保存组件之间的共享数据,如果组件之间有要共享的数据,可以把数据保存到 Vuex 中,Vuex就是提供一个全局的共享数据存储区域,相当于一个数据仓库,各个组件都可以从中读取数据。
16.1 安装Vuex
1 安装vuex
shell
# npm方式安装
npm install vuex@3 --save
# yarn方式安装
yarn add vuex@3Vue2 中只能使用 vuex3版本。
2 创建数据存储对象
如果在一开始新建项目的时候,就勾选了Vuex,那么会在项目的 src 下创建一个 store 目录,并包含了一个 index.js文件。
所以如果你创建项目的时候,没有勾选,那么我们也按照这样的方式来操作。首先在 src 下创建一个 store 目录,然后在其下创建一个 index.js文件,内容如下:
js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {},
getters: {},
mutations: {},
actions: {},
modules: {},
});在上面的代码中,是时候用Vuex创建了一个数据存储对象,在其中有一些属性选项,这个后面再解释。
其中:
state属性是用来存储的公共数据;mutations属性是用来操作state中的数据的方法,只能通过mutations中提供的方法来操作state中的数据;
3 挂载数据存储对象
在项目的 main.js 中引入 Vuex 的数据存储对象,并挂载到Vue实例上。
js
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')我们已经将 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 :
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: {},
});mutations中的方法最多只能有两个参数,第一个参数是固定的,就是state对象,第二个参数我们在调用的时候可以传值过来,如果想传递多个值,只能通过对象的形式来传值,在对象中定义多个属性。怎么调用
mutations中的方法,后面会讲到;
2 定义两个组件
编写 CounterCom.vue 组件,在组件中只是通过 $store.state.count 来获取 store 中 state 中的 count 数据,还使用了一个 input 绑定了 count 的数据,这样 state 中的数据发生了变化就会显示在 CounterCom.vue 组件上。
代码如下:
vue
<template>
<div>
<!-- 通过$store.state.属性获取数据 -->
<h3>{{ $store.state.count }}</h3>
<!-- 和store中的值实现绑定 -->
<input type="text" v-model="$store.state.count">
</div>
</template>
<script>
</script>注意:form元素不能和state中的属性实现双向绑定,如果要实现双向绑定,需要使用组件和组件自己data中的数据实现双向绑定,同时监听组件中数据的变化,然后修改state中的数据。上面的不规范。
创建 OperateCom.vue 文件,主要是用来操作 Vuex 中的数据。
编写代码如下:
vue
<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>在组件中定义了增减和减少的按钮,当点击按钮的时候,调用组件中定义的方法,在定义的方法中操作 store 中的数据,通过this.$store.commit 来调用 store 中 mutations 中定义的方法。
3 使用两个组件
我就直接在 App.Vue 中演示了,在其中引入并使用这两个组件;
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>运行项目,当点击OperateCom组件中的增加和减少的按钮的时候,CounterCom中显示的数值会自动变化。
16.3 getters属性
当从 Vuex 中获取数据的时候,想要做一些转换,可以使用 getters ,例如在之前的 state 中存储的 count,如果想要获取 count 的时候,获取到的值前面加上 总计: 那么就可以使用 getters。
在 store 中定义 getters 属性,在属性中定义对应的属性及对应的函数。
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: {},
modules: {},
});通过上面定义的 getter,在获取的时候,就可以通过 $store.getters.optCount 来获取转换后的 count 的值了。
在 CounterCom 组件中,可以获取转换后的 count 值,当然也可以直接获取 count 的值
vue
<template>
<div>
<!-- 获取转换后的count的值 -->
<h3>{{ $store.getters.optCount }}</h3>
<br>
<!-- 直接获取count的值 -->
<input type="text" v-model="$store.state.count">
</div>
</template>显示效果:

总结:
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属性
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 在组件中调用 Actions
在组件中,你可以通过 this.$store.dispatch 来调用 actions。
vue
<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>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
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;
}
}
}goods.js
js
// goods.js
export default {
namespaced: true, // 需要开启命名空间
state: {
goodsList: [
{ id: 1, name: "旺旺大礼包", price: 28.9 },
// ...其他商品
]
},
mutations: {
// ...商品相关的mutations
},
actions: {
// ...商品相关的actions
},
getters: {
// ...商品相关的getters
}
}2 注册模块
在 src/store 目录下的 index.js 文件中注册这些模块。
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
}
});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:
vue
<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>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 配置如下:
js
// 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: {},
});下面通过 mapState、mapGetters、 mapMutations 、mapActions 简化 Vuex 的调用。
在组件中使用如下:
vue
<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 多模块
如果 Vuex 分为多个模块。
还是沿用上面的 user 和 goods 模块。
src/user.js
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;
}
}
}src/goods.js
js
// goods.js
export default {
namespaced: true, // 开启命名空间
state: {
goodsList: [
{ id: 1, name: "旺旺大礼包", price: 28.9 },
// ...其他商品
]
},
mutations: {
// ...商品相关的mutations
},
actions: {
// ...商品相关的actions
},
getters: {
// ...商品相关的getters
}
}待会只演示调用 user 模块就可以了,调用 goods 模块也是一样的。
src/index.js
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
}
});在组件中使用如下:
映射的时,需要指定模块的名称。
vue
<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>如果多个模块中的 state、getters 、 mutations、actions 存在重名呢?
js
//----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'
})内容未完......