1、定义:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
2、特点:
(1)能够在vuex中 集中管理共享的数据,易于开发和后期维护
(2)能够高效地实现组件之间的数据共享, 提高开发效率
(3)存储在 vuex中的数据都是响应式的,能够实时保持数据与页面的同步
(4)一般情况下,只有组件之间共享的数据,才有必要存储到vuex中;
(5)对于组件中的私有数据,依旧存储在组件自身的data中即可.
3、使用场景
如果你实在不知道开发中什么数据才应该保存到Vuex中,那你就先不用Vuex,需要的时候自然就会想到它;为了方便实现组件之间的数据共享,Vuex是他们团队借鉴了redux,用来实现vue组件全局状态(数据)管理的一种机制.
4、这个状态自管理应用包含以下几个部分:
state,驱动应用的数据源;存放数据,组件之间共用的状态,与组件各自之间的关系无关
view,以声明方式将 state 映射到视图;
actions,响应在 view 上的用户输入导致的状态变化。
组件(模板)view 数据发生改变-----------actions(vue+vuex)--------state(vuex)----组件(模板)view;
5、使用步骤:
(1)要使用vuex就要先安装--------cnpm install vuex --save
(2)创建使用store使用state
在src下创建store文件夹,并在文件夹下创建index.js 填入以下代码
import Vue from 'vue'
// 引入vuex
import Vuex from 'vuex'
// 启用vuex
Vue.use(Vuex)
// 创建vuex实例
export default new Vuex.Store({
state: {},
mutations: {},
actions: {},
modules: {}
})
(3)将这个vuex实例引入main.js并挂在到vm对象上
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
/** 引入仓库实例 */
import store from './store'
const app = createApp(App);
app.config.unwrapInjectedRef = true
app.use(store).use(router).mount('#app')
简单使用:在store/index.js state对象中写上一个状态
state: {
isShowHeader: false
}
在组件中view上通过$store.state.isShowHeader获取并使用,通过点击事件更改状态
<template>
<div id="app">
<div v-if="$store.state.isShowHeader" class="header">
<ul>
<li>首页</li>
<li>游戏</li>
<li>下载</li>
<li>我的</li>
<li>联系我们</li>
</ul>
</div>
<button @click="changeShowHeaderState">点击切换状态</button>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
obj: {
ages: {
age: 18
}
}
}
},
watch: {
'$store.state.isShowHeader': function() {
},
'obj.ages.age': function() {
}
}
methods: {
changeShowHeaderState() {
const state = this.$store.state.isShowHeader;
const age = this.obj.ages.age;
this.$store.state.isShowHeader = state === true ? false : true;
}
}
};
</script>
在此后,无论哪一个组件更改了,在其他组件就会拿到最新的状态值。
举个复杂一点的小栗子:在src下的store文件夹里面创建一个购物功能的仓库,并在组件里面使用它的功能。
interface itemBuyNumTypeI {
index: number;
n: number;
}
interface itemTypeI {
id: number;
title: string;
num: number;
price: number;
cover: string;
}
export default createStore({
state: {
cars: [] as Array<itemTypeI>
},
getters: {
totalNum(state) {
return state.cars.reduce((pV, currentItem) => pV + currentItem.num, 0);
},
totalMoney(state) {
return state.cars.reduce((pV, currentItem) => pV + currentItem.num * currentItem.price, 0);
}
},
mutations: { /** 不能有异步处理 */
addItem2Cars(state, item) {
console.log(state);
const ind = state.cars.findIndex(p => p.id === item.id);
/** 没找着直接追加 */
if (ind === -1) {
state.cars.push(item);
} else {
/** 找到了,就修改购买数量 */
state.cars[ind].num += item.num;
}
},
itemBuyNum(state, pidN: itemBuyNumTypeI) {
// 至少是 1
if (state.cars[pidN.index].num === 1 && pidN.n == -1) return;
state.cars[pidN.index].num += pidN.n;
},
/** 购物车初始化 */
carsInit(state, itemArr) {
state.cars = JSON.parse(JSON.stringify(itemArr));
}
},
actions: { /** 异步处理 */
carsInit(context) {
/** 如果已经有数据了,就不用初始化了 */
if (context.state.cars.length) return;
getCarsItems().then(res => {
console.log(res);
/** 修改数据还是必须且只能通过mutation */
context.commit('carsInit', res.data);
}).catch(err => {
console.log(err);
});
}
},
modules: {
}
})
在组件里面使用:
<template>
<div>
我是A组件,数据: <span>{{ msg }}</span>
<ul class="cars">
<li v-for="(p, ind) in cars" :key="p.id">
<div class="left">
<img :src="p.cover" :alt="p.title" />
</div>
<div class="right">
<div class="top">
{{ p.title }}
</div>
<div class="num">
购买数量:
<div>
<button @click="itemNumInc({ index: ind, n: -1 })">--</button>
<input type="number" v-model="p.num" />
<button @click="itemNumInc({ index: ind, n: 1 })">++</button>
</div>
</div>
<div class="price">价格:{{ p.price }}</div>
</div>
</li>
<li>
<div class="left">商品总数:{{ totalNum }}</div>
<div class="right">商品总价:{{ totalMoney }}</div>
</li>
</ul>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
interface itemTypeI {
id: number;
title: string;
num: number;
price: number;
cover: string;
}
interface itemBuyNumTypeI {
index: number;
n: number;
}
export default defineComponent({
components: {},
/** 我们通过计算属性使用Vuex里面的数据*/
computed: {
cars(): Array<itemTypeI> {
return this.$store.state.cars;
},
totalNum(): number {
return this.$store.getters.totalNum;
},
totalMoney(): number {
return this.$store.getters.totalMoney;
},
},
data() {
return {
msg: '我是组件A的数据',
};
},
methods: {
itemNumInc(pidN: itemBuyNumTypeI) {
this.$store.commit('itemBuyNum', pidN);
},
},
mounted() {
//初始化购物车历史数据
this.$store.dispatch('carsInit');
},
});
</script>
(4)解剖--state、mutation 、action和getters
i.State:{}存放数据,组件之间共用的状态,与组件关系无关.
ii.Getters:{}-- 相对于vue里面计算属性computed
大家可能会发现我们的流程图中并没有getter,那getter到底是什么呢?
Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
场景:就像上面举的栗子一样,我们需要计算得到商品购买数量和价格时就要使用到
iii.Mutations:{}不同组件里面对数据进行修改(唯一的修改数据的方式),用了action也还要在mutation里面去初始方法)---必须是同步的,必须有载荷和参数。
那为什么通过mutation来修改呢?因为mutation会被DevTools记录,方便查找问题(DevTools,打开github,搜索devtools,翻到项目后边可以下载对应浏览器的tool需要翻墙)
iv.Actions:支持同步和异步,最终要修改数据还是要通过mutation来完成。
类似于 mutation,不同在于:
(1) Action 提交的是 mutation,而不是直接变更状态。
(2)Action 可以包含任意异步操作
使用方式:
view层通过this.$store.dispatch('getListDataAction'); 触发事件为getListDataAction的action
在action中发起异步请求,获取数据,然后调用store.commit('listDataMutation', res.data);(store为action的参数,vuex自自动传入)
触发事件为listDataMutation的mutation
mutation中将提交需要更改的state状态
state状态更新成功后映射到页面上
案例:组件之间来回切换时,如果组件中的数据来自于服务器,就意味中一直切换将会一直发起数据,如果有一个东西可以缓存起来的话,在下一次切换时就不会在发起请求了
6、缺点:团队项目中,如果有谁改了这个状态出bug了,那谁知道是谁改的呢?这个全局变量很好用,但是团队项目中还是比较容易出问题,而且一旦出问题就很难排查,所以需要引入一套管理机制,去负责修改这些状态