一、Vuex是什么?
专门在 Vue 中实现集中式状态(数据)管理的一个 Vue 插件,对 vue 应 用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方 式,且适用于任意组件间通信。其实可以理解 “共享” 数据。
二、Vuex的原理
1、vuex 原理
vuex 所有的数据操作必须通过 action -> mutation -> state(响应式数据) 的流程来进行,再结合 Vue 的数据视图双向绑定特性来实现页面的展示更新。
2、vuex原理图
原理/流程解析:
Vuex包含3个主要部分Actions、Mutations、State
- Actions:用于响应组件中的动作,间接更新state(数据),包含同步/异步操作,支持多个同名方法,按照注册的顺序依次触发;
action 并不是直接修改数据,而是通过 mutations 去修改,这是需要注意的
- Mutations:用于操作数据,直接操作更新state(数据),是Vuex修改state的唯一推荐方法,不可进行异步操作,且方法名只能全局唯一;
- State:用于存储数据,页面上显示的所有数据从State对象中读取,方法名也是全局唯一;
- Vue Components:Vue组件,页面上负责接收用户操作的交互行为,执行dispatch方法触发对应的Actions进行回应;
- Dispatch:操作行为触发方法,是唯一能执行Actions的方法;
- Commit:状态改变提交操作方法,对mutations进行提交,是唯一能执行mutations的方法;
- Devtools:官方的调试工具,非常方便地去调试项目中Vuex的数据变化,主要用来检测Mutations的;
- Vue Components(组件),在组件中 $store . dispatch ( '对应的 action 回调名' ) 触发Actions来响应组件中的动作;
- 在Actions使用commit ( '对应的 mutations 方法名' ) 触发mutations;
- mutations内部操作数据State,之后Vue内部将操作后的数据render渲染到页面上。
可以通过这个通俗例子理解Vuex:
Vue Components相当于是去餐厅吃饭的客人,Actions相当于餐厅的服务员,Mutations相当于餐厅的后厨,菜好不好吃关键就在后厨,State相当于客人点的菜。
客人进门了说话(注:说话就相当于调用dispatch)要两份蛋炒饭(注:相当于dispatch('jia',2)),服务员接受信息并通过点菜系统提交菜单:两份蛋炒饭(注:服务员提交菜单相当调用commit,commit('JIA',2)),于是点菜信息传到了后厨,这个时候后厨将菜进行了一系列加工把菜(注:菜相当于State,加工相当修改State)做好了,最后呈现给客户。
如果客人和餐厅足够熟悉了,他可以不通过服务员,直接跟后厨点菜(注:也就是说Actions响应的是组件的动作,并不是必须的存在的,组件可以直接提交mutations,不需要经过actions这一步),只要一种情况必须通过服务员,那就是餐厅换了菜系,之前的菜都不上了,客人必须通过服务员才能点菜单(注:新菜单其实就相当于backend api 服务器里面的数据,可以异步操作).
三、Vuex的用法
- 在src下新建一个文件夹store,在store下创建一个文件index.js,基本代码如下
基本代码:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
// 创建并暴露store
export default new Vuex.Store({
// actions--用于响应组件中的动作(相当于:服务员)
actions: {
//...
},
// mutations--用于操作数据(相当于厨师)
mutations: {
//...
},
// state-- 用于存储数据(相当于:顾客点菜的菜单)
state: {
//..
},
// getters--用于将state中的数据进行加工(相当于计算属性)
getters: {
//..
}
})
比如:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
// 创建并暴露store
export default new Vuex.Store({
// actions--用于响应组件中的动作(相当于:服务员)
actions: {
jiaOdd(context, value) {
console.log(context, value);
// context:vuex上下文,即整个vuex里面的配置,所有vuex里面的都可以使用
// value:你的传参
console.log('actions的jiaOdd被调用了');
if (context.state.sum % 2) {
context.commit('JIA', value)
}
},
},
// mutations--用于操作数据(相当于厨师)
mutations: {
JIA(state, value) {
// value:你的传参
console.log('mutations的JIA被调用了');
state.sum += value
},
},
// state-- 用于存储数据(相当于:顾客点菜的菜单)
state: {
sum: 0,//当前的和
},
// getters--用于将state中的数据进行加工(相当于计算属性)
getters: {
bigSum(state) {
return state.sum * 10
},
}
})
- 在main.js中引入store
main.js:
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
- 在组件中使用
<template>
<div>
<h3>vuex学习:求和案例</h3>
<p>当前求和:{{$store.state.sum}}</p>
<select v-model.number="n">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="increment">+</button>
<button @click="incrementOdd">当前求和为奇数再加</button>
</div>
</template>
<script>
export default {
data(){
return{
n:1//用户选择数字
}
},
methods:{
increment(){
console.log('+');
// 通过this.$store.commit调用mutations里的方法
this.$store.commit('JIA',this.n)
},
incrementOdd(){
console.log('当前求和为奇数再加');
// 通过this.$store.dispatch调用actions里的方法
this.$store.dispatch('jiaOdd',this.n)
},
}
}
</script>
- 关于辅助函数
vuex使用的另一种更简便的用法,可以借助辅助函数:mapState, mapGetters, mapActions, mapMutations
<template>
<div>
<h3>vuex学习:求和案例_mapActions和mapMutations</h3>
<p>当前求和:{{ sum }}</p>
<p>当前求和方法10倍:{{ $store.getters.bigSum }}</p>
<p>我在{{ school }},学习{{ subject }}</p>
<select v-model.number="n">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="increment(n)">+</button>
<button @click="decrement(n)">-</button>
<button @click="incrementOdd(n)">当前求和为奇数再加</button>
<button @click="incrementWait(n)">等一等再加</button>
</div>
</template>
<script>
import { mapState, mapGetters, mapActions, mapMutations } from "vuex";
export default {
data() {
return {
n: 1, //用户选择数字
};
},
computed: {
// sum(){
// return this.$store.state.sum
// },
// school(){
// return this.$store.state.school
// },
// subject(){
// return this.$store.state.subject
// },
// 借助mapState生成计算属性,从state中读取数据.
// 对象写法(计算属性值和state的值不一致):
// ...mapState({a:'sum',haha:'school',na:'subject'})
// 数组写法(计算属性值和state的值必须保持一致):
...mapState(["sum", "school", "subject"]),
// 借助mapGetters生成计算属性,从getter中读取数据.
// 对象写法:
// ...mapGetters({ bigSum: "bigSum" }),
...mapGetters(["bigSum"]),
},
methods: {
// increment() {
// // 通过this.$store.commit调用mutations里的方法
// this.$store.commit("JIA", this.n);
// },
// decrement() {
// this.$store.commit("JIAN", this.n);
// },
//借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法)
// 参数通过调用方法的时候传进来
...mapMutations({increment:'JIA',decrement:'JIAN'}),
// (数组写法)
// ...mapMutations(['JIA','JIAN']),
// incrementOdd() {
// console.log("当前求和为奇数再加");
// // 通过this.$store.dispatch调用actions里的方法
// this.$store.dispatch("jiaOdd", this.n);
// },
// incrementWait() {
// console.log("等一等再加");
// this.$store.dispatch("jiaWait", this.n);
// },
//借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(对象写法)
...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
//(数组写法)
// ...mapActions(['jiaOdd','jiaWait'])
},
created() {
console.log("mapState=", mapState);
},
};
</script>
<style>
</style>
四、Vuex的应用场景/什么时候该用Vuex
- 多个组件依赖统一数据或状态时;
- 来自不同组件的行为需要变更同一状态时;
五、Vuex的持久性储存
Vuex的数据,会有一个问题:页面或浏览器刷新时,数据会丢失
解决方案:借助插件vuex-persistedstate对Vuex的数据进行持久性储存
补充:在Vue3中不推荐使用Vuex,官方推荐Pinia 。
因为Vuex在 API 的设计上,对TS 的类型推导的支持比较复杂,在TS会用起来很痛苦。为了解决这个问题,Vuex的作者发布了Pinia,并称为下一代Vuex。
六、Vue思考题
1、Vuex的actions和mutations有什么区别?
- actions主要用于响应组件中的动作,通过 commit( )来触发 mutation 中函数的调用, 间接更新 state,不是必须存在的,;
mutations主要用于直接操作数据,修改数据,是必须存在的;
- actions可以进行异步操作,可用于向后台提交数据或者接受后台的数据;
mutations中是同步操作,不能写异步代码、只能单纯的操作 state ,用于将数据信息写在全局数据状态中缓存,不能异步操作;