文章目录
全局状态管理模式Vuex
💡 Tips:由于需要使用到全局变量,vue3做全局状态管理和它搭配最好的是Pinia,但是菠萝是和组合式API搭配一起使用更好一些。和Pinia功能一模一样的是Vuex插件,这个插件公司相对使用得较多。
- vue3对应的是vuex4版本,vue2对应的是vuex3版本
- vue3的官网是找不到vuex,在vue2的官网https://vuex.vuejs.org/zh/
vuex是什么?
Vuex是专门为Vue设计的状态管理模式+库。我们通常称之为全局状态管理模式,它能管理所有组件的状态。(状态在vue里面可以理解为响应式数据)
什么是“状态管理模式”?
状态管理自管理应用包含三部分:
- 状态,驱动应用的数据源
- 视图,以声明方式将状态映射到视图
- 操作,响应式的在视图上的用户输入导致的状态变化
当我们的应用遇到多个组件共享状态(数据)时,单项数据流的简洁性很容易被破坏。意思是说vue是单项数据流,数据只能从父的去改变子的,子的是不能操作父的。
vuex的应用场景
- 多个视图(组件)依赖于同一个状态(数据)
- 来自不同视图(组件)的行为需要变更同一状态(数据),指的是多个组件需要更改同一个数据
问题一:传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力。
问题二:我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式非常脆弱,通常会导致无法维护的代码。
因此,我们把组件的共享状态抽取出来,以一个全局单例模式管理
Vuex安装
- 项目安装命令npm init vue@latest
- 依赖安装npm i
- Vuex安装npm install vuex@next --save
开始
可以创建一个store文件夹存放index.js文件
// 从vuex里面解构createStore方法
import { createStore } from "vuex";
// 创建仓库实例
const store = createStore()
// 暴露实例
export default store;
从入口文件main.js引入仓库实例
这里注意:如果路径默认以文件夹结尾,例如这里的./store相当于./store/index.js
import { createApp } from "vue";
import App from "./App.vue";
// 引入仓库实例
import store from "./store";
createApp(App).use(store).mount("#app");
仓库的state就是全局的数据,相当于组件的data,语法是data一样是函数返回对象
// 要创建仓库实例
import { createStore } from "vuex";
const store = createStore({
// 仓库的state就是全局的数据,相当于组件的data
state() {
return {
count: 10,
};
},
});
export default store;
在跟组件里面引入两个子组件
<template>
<h2>vuex-demo</h2>
<Child1 />
<hr />
<Child2 />
</template>
<script>
import Child1 from "./components/Child1.vue";
import Child2 from "./components/Child2.vue";
export default {
components: {
Child1,
Child2,
},
};
</script>
通过this. s t o r e 在子组件中拿到仓库的数据,这里的 t h i s . store在子组件中拿到仓库的数据,这里的this. store在子组件中拿到仓库的数据,这里的this.store是仓库实例是个对象类似于this.$route
- 可以通过this.$store.state.xxx来获取仓库数据
- 计算属性会根据已有的值去计算一个新的结果,并且已有的值可以作为计算属性的依赖
export default {
mounted() {
return this.$store.state.count;
},
computed: {
count() {
return this.$store.state.count;
},
},
}
- 仓库数据不能使用data来接收,会导致响应式失效
// 仓库数据不能使用data来接受,会导致响应式失效
data() {
return {
count: this.$store.state.count,
};
},
- 直接修改是不符合规范的
methods: {
fn() {
// 直接修改是不符合规范
this.$store.state.count++;
},
},
- mutations是唯一可以改变state的地方,里面放函数,子组件可以通过commit方法去调用这个函数改变仓库数据
mutations: {
increment(state) {
state.count++;
},
}
methods: {
fn() {
this.$store.commit("increment");
},
}
核心概念
一、State
1、单一状态树
- 一个项目里面只有一个仓库,只有一个state。
- 单一状态树和模块化并不冲突,项目会分模块,意味着会有多个state
2、在 Vue 组件中获得 Vuex 状态
- 由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态
3、mapState辅助函数
💡Tips:当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。可以使用 mapState 辅助函数帮助我们生成计算属性,较少代码量
- mapState数组写法
- 当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组。
// 重复和冗余
// computed: {
// count() {
// return this.$store.state.count;
// },
// name() {
// return this.$store.state.name;
// },
// sex() {
// return this.$store.state.sex;
// },
// likes() {
// return this.$store.state.likes;
// },
// },
// mapState的数组写法
computed: mapState(["count", "name", "sex", "likes"]),
- mapState对象写法
- 可以重命名
- 可以写函数,改造数据
computed: mapState({
// 重命名
num: "count",
// 函数写法用于做数据的改造
name: (state) => "亲爱的" + state.name,
sex: (state) => {
return state.sex === 1 ? "男" : "女";
},
// 如果需要用到this,要把箭头函数转成普通函数
likes(state) {
return state.likes.concat(this.like);
},
}),
由于组件自身就可以写computed属性,但是组件里面的选项是不能重复的,如果我们写两个computed那么后面的对象肯定会覆盖前面的。
解决方法:使用扩展运算符或者Object.assign() 浅拷贝合并对象
computed: {
...mapState({
// 重命名
num: "count",
// 函数写法用于做数据的改造
name: (state) => "亲爱的" + state.name,
sex: (state) => {
return state.sex === 1 ? "男" : "女";
},
// 如果需要用到this,要把箭头函数转成普通函数
likes(state) {
return state.likes.concat(this.like);
},
}),
doubleNum() {
return this.num2 * 2;
},
},
二、Getter
场景:如果我们的各自组件都需要封装一个相同的函数,例如计算属性里面封装一个时间戳函数,我们需要在其他组件中复用这个函数,这样重复的代码就很多,这是我们希望将函数写在仓库里给其他组件使用。
- getters相当于仓库的计算属性
- state这个参数就是为了拿到state(){}里面的数据
- getters这个参数表示一个getters不仅可以依赖仓库的数据,还可以依赖另一个getters。(一般用不上)
- getters传参
- mapGetters 辅助函数,mapGetters仅仅是将 store 中的 getter 映射到局部计算属性
// getters相当于是仓库的计算属性
getters: {
timeStr(state, getters) {
const date = new Date(state.time);
const Y = date.getFullYear() + "-";
const M =
(date.getMonth() + 1 < 10
? "0" + (date.getMonth() + 1)
: date.getMonth() + 1) + "-";
const D =
(date.getDate() < 10 ? "0" + date.getDate() : date.getDate()) + " ";
const h =
(date.getHours() < 10 ? "0" + date.getHours() : date.getHours()) + ":";
const m =
(date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes()) +
":";
const s =
date.getSeconds() < 10 ? "0" + date.getSeconds() : date.getSeconds();
return Y + M + D + h + m + s;
},
timeStr2(state, getters) {
return getters.timeStr + "!!!";
},
count2: (state) => (n) => state.count * n,
});
三、Mutation
mutation是唯一可以改变state的方法。mutation里面是放函数,通过this.store.commit方法去触发mutation里面的函数。
1、提交载荷(Payload)
- 可以接受第二个参数payload(载荷),表示参数
- 一般情况下payload建议写成对象的形式
- 对象可以传多个值
- 方便维护
syncAgeAdd(state, payload) {
state.age += payload.n;
},
2、对象风格的提交方式
add(n) {
// 如何去触发仓库的mutations的函数
this.$store.commit("increment2", { num: n });
// 等价于下面的对象风格的提交方式
this.$store.commit({
// type: "increment2",
type: INCREMENT2,
num: n,
});
},
3、使用常量代替Mutation事件类型
使用常量将字符串存起来使用
export const INCREMENT2 = "increment2";
4、Mutation必须是同步函数
- 每一次调用mutation的函数的时候,在开发工具里面都会产生一条记录(就是快照)
- 产生快照的时间是mutation函数调用的时间,而不是数据改变的时间。
- 会让开发工具里面快照的值有错误(例如下面使用定时器异步代码)
addage(state) {
setTimeout(() => {
state.age++;
}, 2000);
},
5、在组件中提交Mutation
- mapMutations辅助函数是使用在methods里面
- 下面代码将仓库的addage函数映射到了组件的methods里面,变成了methods里面有addage函数
methods: {
ageadd() {
// this.$store.commit("addage");
this.addage();
},
// 将仓库的addage函数映射到了组件的methods里面,
// 变成了methods里面有addage函数
...mapMutations(["addage", INCREMENT2]),
}
四、Action
action类似于mutation,不同的在于:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作。
- 通过dispatch的方法来调用仓库的actions的函数
- actions是不能直接修改state的,只能通过调用mutations的函数
<template>
<h3>actions</h3>
<div>age: {{ age }} - <button @click="fn">async age++</button></div>
</template>
<script>
import { mapState, mapActions } from "vuex";
export default {
computed: mapState(["age"]),
methods: {
fn() {
// 通过dispatch的方法来调用仓库的actions的函数
// this.$store.dispatch("asyncAgeAdd", { n: 4 });
this.asyncAgeAdd({ n: 4 });
},
...mapActions(["asyncAgeAdd"]),
},
};
</script>