什么是Vuex?
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化
这个状态自管理应用包含以下几个部分:
state,驱动应用的数据源;
view,以声明方式将state 映射到视图;
actions,响应在 view 上的用户输入导致的状态变化。
components: {
cpn: {
// state
data() {
return { num: 123 }
},
// view
template: '<div>{{num}} <button @click="add">add</button></div>',
// actions
methods: {
add() {
this.num++
}
}
}
}
但是,当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:
- 多个视图依赖于同一状态。
- 来自不同视图的行为需要变更同一状态。
对于问题一,传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力。
对于问题二,我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式非常脆弱,通常会导致无法维护的代码。
因此,我们为什么不把组件的共享状态抽取出来,以一个全局单例模式管理呢?在这种模式下,我们的组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为!
通过定义和隔离状态管理中的各种概念并通过强制规则维持视图和状态间的独立性,我们的代码将会变得更结构化且易维护。
每一个 Vuex 应用的核心就是 store(仓库/商店)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。Vuex 和单纯的全局对象有以下两点不同:
- Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
- 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutaition。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用
Vuex 使用单一状态树——是的,用一个对象就包含了全部的应用层级状态。至此它便作为一个“唯一数据源(SSOT)”而存在。这也意味着,每个应用将仅仅包含一个store 实例。单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。
单状态树和模块化并不冲突——在后面的章节里我们会讨论如何将状态和状态变更事件分布到各个子模块中。
如下案例,假如我们在多个页面都想去获取到一个共同的数据,我们可以把这个数据放到store中
store/state.js
export default{
str:'这是vuex的数据',
}
Home.vue
<h1>{{ $store.state.str }}</h1>
About.vue
<h1>{{ $store.state.str }}</h1>
此时二者都可以访问到
在js中也可以访问到
......
mounted() {
console.log(this.$store.state.str);
}
......
核心模型之state
当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性
computed: { ...mapState(["str", "status1", "status2"]) }
<h1>{{ str }}</h1>
<h1>{{ status1 }}</h1>
<h1>{{ status2 }}</h1>
js中也可以直接访问
console.log("map", this.str);
核心模型之getter
有时候我们需要从store 中的 state中派生出一些状态,getter里面放的是计算属性,
比如说我现在想把一个时间戳变为我们能看懂的格式:
常规的计算属性操作:
<h1>{{ new Date().getTime() }}</h1> //1629189714804
<h1>{{ now_date }}</h1>
computed: {
now_date() {
return new Date(1629189714804).toLocaleString();
},
},
通过getters操作:
store/getters.js
export default {
setStr(state){
console.log(state);
return state.str+'拿到了吗?计算了吗?'
}
}
mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性:
Home.vue
......
import { mapState, mapGetters } from "vuex";
......
computed: {
...mapState(["str", "status1", "status2"]),
...mapGetters(["setStr"]),
},
如果你想将一个 getter 属性另取一个名字,使用对象形式:
computed: {
...mapState(["str", "status1", "status2"]),
...mapGetters({ Str: "setStr" }),
},
核心模型之mutation
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:
比如我们现在有这样一个需求,需要点击按钮,在页面显示对应的信息:
state.js
......
list:[
{name:'张三'},
{name:'李四'},
{name:'王五'},
],
......
mutaitions.js
set_name(state ,name){
state.user_name = name
},
about.vue
......
<ul>
<li v-for="item in list" :key="item.name" @click="btn(item.name)">
{{ item.name }}
</li>
</ul>
<h1>{{ user_name }}</h1>
......
import { mapState, mapMutations } from "vuex";
......
methods: {
...mapMutations(["set_name"]),
btn(name) {
this.set_name(name);
},
},
提交多个参数:
我们可以传入额外的参数,即mutation 的 载荷(payload),在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的mutation 会更易读:
......
this.set_name({ name, a: 123 });
......
使用常量替代
mutation 事件类型是很常见的模式。同时把这些常量放在单独的文件中可以让你的代码合作者对整个app 包含的 mutation一目了然:
.....
store.commit('fn1')
.....
核心模型之action
Action 类似于 mutation,不同在于:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作。
先来看一下action的使用
我们先来用一下actions
<template>
<div class="about">
<h1>22222</h1>
<h1>{{ a }}</h1>
<button @click="btn">按钮</button>
</div>
</template>
<script>
import { mapState, mapMutations } from "vuex";
export default {
computed: {
...mapState(["a"]),
},
methods: {
btn() {
console.log(123);
},
},
};
</script>
我们在mutaitions里面写了一个add方法去给state里的一个数据做累加
add(state){
state.a++
}
我们可以直接使用mapMutations去调用方法,那么我们怎样通过action提交mutaitions呢?
我们需要新建一个actions.js
export default {
add_fn({commit}){
commit("add")
}
}
在页面中我们需要使用时可以按照原来的方法使用mapMutations,但是我们使用的是action,所以使用mapActions
<script>
import { mapState, mapMutations, mapActions } from "vuex";
export default {
computed: {
...mapState(["a"]),
},
methods: {
...mapActions(["add_fn"]),
btn() {
console.log(123);
this.add_fn();
},
},
};
</script>
以上是action的一个使用,他是用来提交mutaition的,而不是直接更改状态,那么他的优势在哪里呢?我们可以在action里面做判断来决定是否提交某些方法.
<button @click="btn(true)">+</button>
<button @click="btn(false)">-</button>
......
btn(boolean) {
this.add_fn({ bool: boolean });
},
......
export default {
add_fn({commit},obj){
if(obj.bool){
commit("add", obj)
}else{
commit("re")
}
}
}
mutaition和action的区别
一条重要的原则就是要记住 mutation 必须是同步函数。
在 mutation 中混合异步调用会导致你的程序很难调试。例如,当你调用了两个包含异步回调的 mutation 来改变状态,你怎么知道什么时候回调和哪个先回调呢?这就是为什么我们要区分这两个概念。在 Vuex 中,mutation 都是同步事务:
我们可以通过案例来看一下:
分别在mutaition和action中添加异步操作来看看区别:
action.js
set_b_action({commit},obj){
setTimeout(()=>{
commit('set_b',obj)
},1000)
}
mutaition.js
set_b(state,obj){
setTimeout(()=>{
state.b += obj.num
},1000)
}
核心模型之module
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割: