「这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战」
前言:
在写稍微复杂的项目时,我们难免会遇到多个组件共享一个数据状态的情况,这样我们可以自己写一个单例模式来保存数据,或者写到全局中,然后去读取数据。但是呢,如果要在一个组件上更改了数据。其他组件立马就可以得到更新,就像Vue的开发模式一样响应式。以写java的经验来看我们就又得写一个观察者模式。这样注册了观察者事件的组件才会得到更新。而Vuex就是这样的一个组件。下面我们就来学习如何使用Vuex.
简单使用
根据Vuex的特性,它是一个响应式的状态管理框架。 那么我们如果在一个页面中显示两个组件,数据都是从store中获取,再添加一个按钮来变更store中的数据。我们观察页面是否都进行了数据的变更。
下面就来进行实现这个小功能.
- 创建一个store(数据中心),一个项目只存在一个store。
import {createStore} from 'vuex';
createStore({
})
- 在main.js中使用use把store载入
import MyStore from './store/MyStore';
let app = createApp(App);
app.use(MyStore);
3.开始编写CteateStore,state是保存需要共享数据的地方
import {createStore} from 'vuex';
export default createStore({
//state是保存数据的地方
state() {
return {
ticketNum: 0,
};
},
//更改 Vuex 的 store 中的状态的唯一方法是提交 mutation
//如果需要更新数据就在这里使用,使用commit方法提交。
mutations: {
add(state) {
state.ticketNum++;
},
/**
* 初始化数据
* @param state
* @param num 票数量
*/
initData(state, num) {
state.ticketNum = num;
},
},
});
- 我们创建一个组件tiket.vue用来显示store中的数据
<template>
<!-- 获取store中的数据并进行显示 我们在另一个组件中使用this.$store.state.ticketNum来获取数据。或者如果要加工state中的数据的话,可以在Getter函数中统一处理,然后再进行获取Getter-->
<div>其他组件票数量:{{ this.$store.state.ticketNum }}</div>
</template>
- 在app.vue中初始化数据并修改数据
<template>
<div>剩余票数:{{ ticketNum }}</div>
<button @click="add">添加票数</button>
<ticket></ticket>
</template>
<script>
import ticket from './components/ticket';
export default {
name: 'App',
data() {
return {};
},
methods: {
add() {
//我们更改store中的数据状态
this.$store.commit('add');
},
},
computed: {
// 获取store中的数据状态
ticketNum() {
return this.$store.state.ticketNum;
},
},
mounted() {
//初始化组件中的数据
this.$store.commit('initData', 10);
},
components: {
ticket,
},
};
</script>
以下就是完成的效果,我们点击一个组件中的方法进行更改store中的数据,另一个组件也会进行更新。
以上就是我们简单的使用Vuex的步骤。下面我们就详细学习vuex中的核心概念
vuex中的核心概念
官网中有这样两点说明:
- Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
- 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。
这两点我们从上面简单的例子中都有体现
State
每个应用将仅仅包含一个 store 实例,而state就是存放全局唯一数据的地方。就和vue组件中的data对象一样。
辅助函数mapState
当我们要从store中获取多个state数据时,可以使用mapState函数简化操作。因为mapState返回的是一个对象,所以我们在computed中使用时应该这样写:
import {mapState} from 'vuex';
computed: mapState({
//使用箭头函数获取state中的值
num: (state) => state.ticketNum,
//映射 key 为 store.state.ticketTotal的值(state中的值以字符串形式存在)
total: 'ticketTotal',
retain: 'ticketRetain',
//用普通的函数this可以指向vue实例,而进行其他操作
tempCount(state) {
return state.ticketTotal + this.temp;
},
}),
使用上面的方法写有一个缺点,如果组件中的计算不使用state中的值,也要写在mapState中是不科学的,所以使用对象展开运算符就会很好的解决这个问题上面的写法更改成下面这种写法:
computed: {
//使用对象展开运算符将此对象混入到外部对象中
...mapState({
//使用箭头函数获取state扣的值
num: (state) => state.ticketNum,
//映射 key 为 store.state.ticketTotal的值(state中的值以字符串形式存在)
total: 'ticketTotal',
retain: 'ticketRetain',
//用普通的函数this可以指向vue实例,而进行其他操作
tempCount(state) {
return state.ticketTotal + this.temp;
},
}),
},
Getter
假如我们要在组件中显示经过store计算的结果,则使用对外提供的getter对象, 这样我们可以在多个组件内使用,而不用在每一个组件内计算。
- 通过属性访问getter中的结果
getters: {
//使用属性方式调用,注意:getter 在通过属性访问时是作为 Vue 的响应式系统的一部分缓存其中的
doneComputeTotal(state) {
return state.ticketTotal - state.ticketRetain + 30;
},
//可以接受一个getters对象做为第二个参数
doneComputeTotal2(state, getters) {
return getters.doneComputeTotal + 20;
},
//使用函数的方式进行调用,不会进行缓存
doneComputeTotal3: (state) => () => {
return state.ticketTotal - state.ticketRetain + 100;
},
},
在组件中通过属性的方式使用this.store.getters.doneComputeTota2
- 通过方法访问getter中的结果
声明getters对象:
getters: {
//使用函数的方式进行调用,不会进行缓存
doneComputeTotal3: (state) => () => {
return state.ticketTotal - state.ticketRetain + 100;
},
},
进行调用:
computed: {
temps() {
return this.$store.getters.doneComputeTotal3();
},
}
辅助函数mapGetters
mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。和mapState的用法一样,mapGetters也是返回一个对象
computed: {
//使用数组的形式,和getters中声明的属性一致
...mapGetters(['doneComputeTotal2', 'doneComputeTotal']),
},
或者
//如果想更改getters中声明的名字可以使用对象的形式
...mapGetters({
don3: 'doneComputeTotal3',
don1: 'doneComputeTotal',
}),
Mutation
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型 (type)和一个回调函数 。这个回调函数就是我们自己声明的方法,并且它会接受 state 作为第一个参数: 注意:mutation中只能提交同步的操作,要使用异步操作则使用action
mutations: {
//add相当于一个回调函数,我们只能通过store.commit("add")进行调用
add(state) {
//更改状态
state.ticketNum++;
},
}
或者使用常量的形式进行声明
[ADD](state){
//更改状态
state.ticketNum++;
}
调用Mutaion传参的话,如果是单一字段,则直接传递。如果是多个字段,则我们在调用的时候以对象的形式传参如:
store.commit('add', {
num: 10
})
或者使用下面的方式提交
this.$store.commit({
type: 'initData',
count: {
num: 20,
},
});
mapMutation
在方法调用的地方使用
methods: {
...mapMutations(['add']),
//或者更改mutation中声明的属性
...mapMutations({
increment: 'add',
}),
},
Action
Action 提交的是 mutation,而不是直接变更状态
Action可以进行异步操作
mutations: {
initData(state, num) {
state.ticketNum = num.count.num;
},
},
actions: {
//es6 参数解构
test({commit}, num) {
commit('initData', num);
},
},
使用
store.dispatch({type:'test',{}})
辅助函数mapAction
methods: {
...mapActions(['test']),
},
在什么情况下使用?
假如我们现在有个需求,是购票时如果用户有兑换券的话,可以使用兑换券,否则使用微信支付购票。退票时根据用户购票方式退票或退款。最好的做法就是在进入首页的时候获取到用户是否有兑换券,然后再购票的时候从store中获取state来展示对应的逻辑是跳转微信支付还是直接使用兑换券。如果购买成功,则在state中减去一张兑换券数量。在退票成功的时候我们在state中加1。这样所有使用到store中的兑换券数量都会同步更新。