文章目录
一.应用状态管理
1.什么是状态管理
- 在开发中,我们会的应用程序需要处理各种各样的数据,这些数据需要保存在我们应用程序中的某一个位置,对于这些数据的管理我们就称之为是 状态管理。
- 在前面我们是如何管理自己的状态呢?-
- 在Vue开发中,我们使用组件化的开发方式;
- 而在组件中我们定义data或者在setup中返回使用的数据,这些数据我们称之为state;
- 在模块template中我们可以使用这些数据,模块最终会被渲染成DOM,我们称之为View;
- 在模块中我们会产生一些行为事件,处理这些行为事件时,有可能会修改state,这些行为事件我们称之为actions;
2.复杂的状态管理
- JavaScript开发的应用程序,已经变得越来越复杂了:
- JavaScript需要管理的状态越来越多,越来越复杂;
- 这些状态包括服务器返回的数据、缓存数据、用户操作产生的数据等等;
- 也包括一些UI的状态,比如某些元素是否被选中,是否显示加载动效,当前分页;
- 当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:
- 多个视图依赖于同一状态;
- 来自不同视图的行为需要变更同一状态;
- 我们是否可以通过组件数据的传递来完成呢?
- 对于一些简单的状态,确实可以通过props的传递或者Provide的方式来共享状态;
- 但是对于复杂的状态管理来说,显然单纯通过传递和共享的方式是不足以解决问题的,比如兄弟组件如何共享数据呢?
3.Vuex的状态管理
- 管理不断变化的state本身是非常困难的:
- 状态之间相互会存在依赖,一个状态的变化会引起另一个状态的变化,View页面也有可能会引起状态的变化;
- 当应用程序复杂时,state在什么时候,因为什么原因而发生了变化,发生了怎么样的变化,会变得非常难以控制和追踪;
- 因此,我们可以考虑将组件的内部状态抽离出来,以一个全局单例的方式来管理:
- 在这种模式下,我们的组件树构成了一个巨大的 “视图View”;
- 不管在树的哪个位置,任何组件都能获取状态或者触发行为;
- 通过定义和隔离状态管理中的各个概念,并通过强制性的规则来维护视图和状态间的独立性,我们的代码边会变得更加结构化和易于维护、跟踪;
4.单一状态树
- Vuex 使用单一状态树:
用一个对象就包含了全部的应用层级的状态,这也意味着,每个应用将仅仅包含一个 store 实例; - 单一状态树的优势:
- 如果你的状态信息是保存到多个Store对象中的,那么之后的管理和维护等等都会变得特别困难;
- 所以Vuex也使用了单一状态树来管理应用层级的全部状态;
- 单一状态树能够让我们最直接的方式找到某个状态的片段;
- 而且在之后的维护和调试过程中,也可以非常方便的管理和维护;
二.Vuex的基本使用
1.安装vuex
- 安装vuex:
npm install vuex
- 挂载vuex:
import { createApp } from "vue";
import App from "./App.vue";
import store from "./store";
createApp(App).use(store).mount("#app");
2.创建Store
- 每一个Vuex应用的核心就是store(仓库):store本质上是一个容器,它包含着你的应用中大部分的状态(state);
- Vuex和单纯的全局对象有什么区别呢?
- 第一:Vuex的状态存储是响应式的
- 当Vue组件从store中读取状态的时候,若store中的状态发生变化,那么相应的组件也会被更新;
- 第二:你不能直接改变store中的状态
- 改变store中的状态的唯一途径就显示提交 (commit) mutation;
- 这样使得我们可以方便的跟踪每一个状态的变化,从而让我们能够通过一些工具帮助我们更好的管理应用的状态;
- 使用步骤:
- 创建Store对象;
- 在app中通过插件安装;
import { createStore } from "vuex";
const store = createStore({
state: () => ({}),
getters: {},
mutations: {},
actions:{},
});
export default store;
3.组件中使用store
- 在组件中使用store,我们按照如下的方式:(具体用法往下看)
- 在模板中使用;
- 在options api中使用,比如computed;
- 在composition api中使用;
三.State
state: () => ({
counter:100
})
1.基本使用
<template>
<div class="home">
//1.在模板中获取状态
<h2>counter当前值为:{{ $store.state.counter }}</h2>
//2.在option api中获取状态(使用计算属性)
<h2>counter当前值为:{{ storeCounter }}</h2>
//3.在setup中获取状态
<h2>Setup中counter当前值为:{{ counter }}</h2>
</div>
</template>
(1)在option api中
<script>
export default {
// 计算属性
computed: {
storeCounter() {
return this.$store.state.counter
}
}
}
</script>
(2)在composition api中
<script setup>
import { useStore } from 'vuex'
import { toRefs } from 'vue';
const store = useStore()
//如果直接解构获取到的counter不是响应式的,所以要使用toRefs
const {counter} = toRefs(store.state)
</script>
2.mapState
- 如果我们有很多个状态都需要获取的话,可以使用mapState的辅助函数:
- mapState的方式一:对象类型;
- mapState的方式二:数组类型;
- 也可以使用展开运算符和原来有的computed混合在一起;
例如:要获取name,age,height状态
state: () => ({
name: "jack",
age: 21,
height: 179,
]
})
<template>
<div class="home">
//1.在option api中
//1.1数组语法
<h2>name:{{ name() }}</h2>
<h2>age:{{ age() }}</h2>
<h2>height:{{ height() }}</h2>
//1.2对象语法
<h2>name:{{ sName }}</h2>
<h2>age:{{ sAge }}</h2>
<h2>height:{{ sHeight }}</h2>
//2.在composition api中
<h2>name:{{ name }}</h2>
<h2>age:{{ age }}</h2>
<h2>height:{{ height }}</h2>
</div>
</template>
(1)在option api中
<script>
import { mapState } from 'vuex'
export default {
computed: {
// 数组语法
...mapState(["name", "age", "height"])
// 对象语法
...mapState({
sName: state => state.name,
sAge: state => state.age,
sHeight:state=>state.height
})
}
}
</script>
(2)在composition api中
<script setup>
import { useStore } from 'vuex'
import { toRefs } from 'vue';
const store = useStore()
const {name,age,height} = toRefs(store.state)
</script>
四.Getters
某些属性我们可能需要经过变化后来使用,这个时候可以使用getters:
1.基本使用
<template>
<div class="home">
<h2>counter*2的值为:{{ $store.getters.doubleCounter }}</h2>
<h2>朋友们的年龄和为:{{ $store.getters.totalAge }}</h2>
<h2>id-1的朋友信息: {{ $store.getters.getFriendById(1) }}</h2>
</div>
</template>
state: () => ({
counter: 100,
name: "jack",
friends: [
{ id: 1, name: "a", age: 18 },
{ id: 2, name: "b", age: 19 },
]
}),
getters: {
//1.基本使用
doubleCounter(state) {
return state.counter*2
},
//2.在getters属性中,获取其他的getters
//jack朋友们的年龄和
message(state,getters){
return `${state.name} friendTotalAge:${getters.totalAge}`
},
//3.getters可以返回一个函数,调用这个函数可以传入参数
//根据id获取某一个朋友的信息
getFriendById(state) {
return function (id) {
const friend = state.friends.find(item => item.id === id)
return friend
}
}
}
2.mapGetters
<template>
<div class="home">
//1.option api中
//1.1数组语法
<h2>counter*2的值为:{{ doubleCounter }}</h2>
<h2>朋友们的年龄和为:{{ totalAge }}</h2>
//1.2对象语法
<h2>counter*2的值为:{{ sDoubleCounter }}</h2>
<h2>朋友们的年龄和为:{{ sTotalAge }}</h2>
//2.composition api中
<h2>counter*2的值为:{{ doubleCounter }}</h2>
<h2>朋友们的年龄和为:{{ totalAge }}</h2>
</div>
</template>
(1)在option api中
<script>
import { mapGetters } from 'vuex'
export default {
computed: {
//数组语法
...mapGetters(["doubleCounter","totalAge"])
//对象语法
...mapGetters({
sDoubleCounter: "doubleCounter",
sTotalAge:"totalAge"
})
}
}
</script>
(2)在composition api中
<script setup>
import { useStore,mapGetters } from 'vuex'
import { computed } from 'vue';
const store = useStore()
// 1.手动映射和绑定
const gettersFns = mapGetters(["doubleCounter","totalAge"])
const newGettersFns = {}
Object.keys(gettersFns).forEach(Key => {
newGettersFns[Key]=computed(gettersFns[Key].bind({$store:store}))
})
const {doubleCounter,totalAge}=newGettersFns
</script>
五.Mutations
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation:
state: () => ({
counter: 100,
name: "jack",
}),
mutations: {
changeName(state,newState) {
state.name=newState
},
incrementCounter(state) {
state.counter++
},
changeInfo(state,newState) {
state.counter = newState.counter,
state.name=newState.name
}
}
1.基本使用
<template>
<div class="home">
<button @click="changeName">修改name</button>
<button @click="incrementCounter">递增counter</button>
<button @click="changeInfo">修改info</button>
<h2>name:{{ $store.state.name }}</h2>
<h2>counter:{{ $store.state.counter }}</h2>
</div>
</template>
<script>
export default {
methods: {
changeName() {
this.$store.commit("changeName","kobe")
},
incrementCounter() {
this.$store.commit("incrementCounter")
},
changeInfo() {
this.$store.commit("changeInfo", {
counter: 30,
name:"abc"
})
}
}
}
</script>
2.mapMutations辅助函数
<template>
<div class="home">
<button @click="changeName('kobe')">修改name</button>
<button @click="incrementCounter">递增counter</button>
<button @click="changeInfo({name:'abc',counter:30})">修改info</button>
<h2>name:{{ $store.state.name }}</h2>
<h2>counter:{{ $store.state.counter }}</h2>
</div>
</template>
(1)在option api中
<script>
import { mapMutations } from 'vuex'
export default {
methods: {
...mapMutations(["changeName", "incrementCounter", "changeInfo"])
}
}
</script>
(2)在setup中
<script setup>
import { mapMutations, useStore } from 'vuex'
const store = useStore()
// 1.手动的映射和绑定
const mutations = mapMutations(["changeName", "incrementCounter", "changeInfo"])
const newMutations = {}
Object.keys(mutations).forEach(key => {
newMutations[key] = mutations[key].bind({ $store: store })
})
const { changeName, incrementCounter, changeInfo } = newMutations
</script>
3.重要原则
- 一条重要的原则就是要记住 mutation 必须是同步函数:
- 这是因为devtool工具会记录mutation的日记;
- 每一条mutation被记录,devtools都需要捕捉到前一状态和后一状态的快照;
- 但是在mutation中执行异步操作,就无法追踪到数据的变化;
六.Actions
- Action类似于mutation,不同在于:
- Action提交的是mutation,而不是直接变更状态;
- Action可以包含任意异步操作;
- 这里有一个非常重要的参数context:
- context是一个和store实例均有相同方法和属性的context对象;
- 所以我们可以从其中获取到commit方法来提交一个mutation,或者通过 context.state 和 context.getters 来获取 state 和getters;
1.基本使用
state: () => ({
counter: 100,
name: "jack",
}),
mutations: {
changeName(state,newState) {
state.name=newState
},
incrementCounter(state) {
state.counter++
},
},
actions: {
incrementAction(context) {
//触发mutations
context.commit("incrementCounter")
},
changeNameAction(context, newContext) {
context.commit("changeName",newContext)
}
}
<template>
<div class="home">
<h2>当前计数: {{ $store.state.counter }}</h2>
<button @click="counterBtnClick">发起action修改counter</button>
<h2>name: {{ $store.state.name }}</h2>
<button @click="nameBtnClick">发起action修改name</button>
</div>
</template>
//基本使用
<script>
export default {
methods: {
counterBtnClick() {
//dispatch触发actions
this.$store.dispatch("incrementAction")
},
nameBtnClick() {
this.$store.dispatch("changeNameAction","abc")
}
}
}
</script>
2.mapActions辅助函数
(1)在option api中
<script>
import { mapActions } from 'vuex'
export default {
methods: {
...mapActions(["incrementAction", "changeNameAction"])
}
}
</script>
(2)在setup中
<script setup>
import { useStore, mapActions } from 'vuex'
const store = useStore()
1.在setup中使用mapActions辅助函数
const actions = mapActions(["incrementAction", "changeNameAction"])
const newActions = {}
Object.keys(actions).forEach(key => {
newActions[key] = actions[key].bind({ $store: store })
})
const { incrementAction, changeNameAction } = newActions
</script>
3.actions的异步操作
- Action 通常是异步的,那么如何知道 action 什么时候结束呢?
- 我们可以通过让action返回Promise,在Promise的then中来处理完成后的操作;
<script setup>
import { useStore } from 'vuex'
// 告诉Vuex发起网络请求
const store = useStore()
store.dispatch("fetchHomeMultidataAction").then(res => {
console.log("home中的then被回调:", res)
})
</script>
actions:{
fetchHomeMultidataAction(context) {
// // 1.返回Promise, 给Promise设置then
fetch("http://XXX/home/multidata").then(res => {
res.json().then(data => {
console.log(data)
})
})
}
}
七.Modules
- 什么是Module?
- 由于使用单一状态树,应用的所有状态会集中到一个比较大的对象,当应用变得非常复杂时,store 对象就有可能变得相当臃肿;
- 为了解决以上问题,Vuex 允许我们将 store 分割成模块(module);
- 每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块;
1.基本使用
const moduleA = {
state:()=>({}),
mutations:{}
actions:{}
getters:{
}
const moduleB = {
state:()=>({}),
mutations:{},
actions:{}
}
const store createstore({
modules:{
a:moduleA,
b:moduleB
}
})
store.state.a // -> moduleA的状态
store.state.b // -> moduleB的状态
2.局部状态
对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象:
mutations:{
changeName(state){
state.name = "abc"
}
},
getters:{
info(state,getters,rootstate){
return `name:${state.name}age:${state.age}height:${state.height}`
}
},
actions:{
changeNameAction({state,commit,rootstate}){
commit("changeName","kobe")
}
}
3.命名空间
- 默认情况下,模块内部的action和mutation仍然是注册在全局的命名空间中的:
- 这样使得多个模块能够对同一个 action 或 mutation 作出响应;
- Getter 同样也默认注册在全局命名空间;
- 如果我们希望模块具有更高的封装度和复用性,可以添加 namespaced: true 的方式使其成为带命名空间的模块:-
- 当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名;
4.修改或派发根组件
- 如果我们希望在action中修改root中的state,那么有如下的方式:
//·这里一共有六个参数
changeNameAction({commit,dispatch,state,rootstate,getters,rootGetters}){
commit("changeName","kobe");
commit("changeRootName",null,{root:true});
dispatch("changeRootNameAction",null,{root:true})
}