1. 理解状态管理
-
在开发中,我们会的应用程序需要处理各种各样的数据,这些数据需 要保存在我们应用程序中的某一个位置,对于这些数据的管理我们就 称之为是 状态管理。
-
在前面我们是如何管理自己的状态呢?
- 在Vue开发中,我们使用组件化的开发方式;
- 而在组件中我们定义data或者在setup中返回使用的数据,这些数 据我们称之为state;
- 在模块template中我们可以使用这些数据,模块最终会被渲染成 DOM,我们称之为View;
- 在模块中我们会产生一些行为事件,处理这些行为事件时,有可能 会修改state,这些行为事件我们称之为actions;
3、Vuex的状态管理
- 管理不断变化的state本身是非常困难的:
- 状态之间相互会存在依赖,一个状态的变化会引起另一个状态的变化,View页面也有可能会引起状态的变化;
- 当应用程序复杂时,state在什么时候,因为什么原因而发生了变化,发生了怎么样的变化,会变得非常难以控制和追踪;
- 因此,我们是否可以考虑将组件的内部状态抽离出来,以一个全局单例的方式来管理呢?
- 在这种模式下,我们的组件树构成了一个巨大的 “试图View”;
- 不管在树的哪个位置,任何组件都能获取状态或者触发行为;
- 通过定义和隔离状态管理中的各个概念,并通过强制性的规则来维护视图和状态间的独立性,我们的代码边会变得更加结构 化和易于维护、跟踪;
- 这就是Vuex背后的基本思想,它借鉴了Flux、Redux、Elm(纯函数语言,redux有借鉴它的思想。
- vuex的状态管理逻辑
5. Vuex 使用单一状态树:
- 用一个对象就包含了全部的应用层级的状态;
- 采用的是SSOT,Single Source of Truth,也可以翻译成单一数据源;
- 这也意味着,每个应用将仅仅包含一个 store 实例;
- 单一状态树的优势:
- 如果你的状态信息是保存到多个Store对象中的,那么之后的管理和维护等等都会变得特别困难;
- 所以Vuex也使用了单一状态树来管理应用层级的全部状态;
- 单一状态树能够让我们最直接的方式找到某个状态的片段;
- 而且在之后的维护和调试过程中,也可以非常方便的管理和维护;
2. Vuex基本使用
- 安装vuex
npm install vuex
- 创建store
- 每一个Vuex应用的核心就是store(仓库):
- store本质上是一个容器,它包含着你的应用中大部分的状态(state);
import { createStore } from "vuex";
const store = createStore({
// state有两种写法
//)写法一: state:()=>({}
// 写法二
state() {
return { counter: 0, }
},
})
export default store;
- app.use(store)
import { createApp } from 'vue'
import App from './App.vue'
import store from './store'
createApp(App).use(store).mount('#app')
2.1 Vuex和单纯的全局对象有什么区别呢?
- 第一:Vuex的状态存储是响应式的。当Vue组件从store中读取状态的时候,若store中的状态发生变化,那么相应的组件也会被更新;
- 第二:你不能直接改变store中的状态
- 改变store中的状态的唯一途径就显示提交 (commit) mutation;
- 这样使得我们可以方便的跟踪每一个状态的变化,从而让我们能够通过一些工具帮助我们更好的管理应用的状态;
4.在tempate模板中使用store中state定义的数据 -> $store.state.counter,不能在script中使用。
<template>
<div class="home">
<!-- $store只能在template中使用 -->
<h2>当前计数:{{ $store.state.counter }}</h2>
<button @click="increment">+1</button>
</div>
</template>
3. 核心概念一State
3.1. 基本使用 $store.state.数据名
<div class="home">
<!-- $store只能在template中使用 -->
<h2>当前计数:{{ $store.state.counter }}</h2>
<button @click="increment">+1</button>
</div>
3.2. 映射使用:计算属性和解构store.state
1.options api
2. composition api
- 法一:
- 法二:
3.3 mapState辅助函数
1. option api
如果我们有很多个状态都需要获取话,可以使用mapState的辅助函数:
- mapState的方式一:对象类型;
- mapState的方式二:数组类型;
<template>
<div class="home">
<!-- 1. 在模板中使用多个状态 -->
<h2>name:{{ $store.state.name }}</h2>
<h2>level:{{ $store.state.level }}</h2>
<h2>avatarURL:{{ $store.state.avatarURL }}</h2>
<hr>
<!-- 2. 计算属性(映射状态:数组语法 -->
<h2>name{{ name }}</h2>
<h2>level:{{ level }}</h2>
<h2>avatarURL:{{ avatarURL }}</h2>
<!--3.计算属性(映射的状态:对象语法) -->
<h2>name{{ sName }}</h2>
<h2>level:{{ sLevel }}</h2>
</div>
</template>
<script>
import { mapState } from 'vuex';
export default ({
data() {
return {
}
},
computed: {
// 映射store里的属性
// 1. 数组的写法
...mapState(['name', 'level', 'avatarURL']),
// 2. 对象的写法
...mapState({
sName: state => state.name,
sLevel: state => state.level,
})
}
})
</script>
<script setup>
</script>
<style lang="less" scoped></style>
- 也可以使用展开运算符和来原有的computed混合在一起;
2. compisitionAPI
在setup中使用mapState
- 方法一: 一步步完成
<template>
<div class="home">
<!--4.setup计算属性(映射的状态:对象语法) -->
<h2>name{{ cName }}</h2>
<h2>level:{{ cLevel }}</h2>
</div>
</template>
<script setup>
import { mapState, useStore } from 'vuex';
import { computed } from 'vue';
//1. 一步步完成
// 映射出name函数,level函数,函数内部通过this.$store.state获取数据
const { name, level } = mapState(['name', 'level'])
const store = useStore();
// 先拿到name函数,再给函数bind绑定this
const cName = computed(name.bind({ $store: store }));
const cLevel = computed(level.bind({ $store: store }));
</script>
- 方法二:封装成useState
// 封装useStatehooks
import {computed} from 'vue'
import { useStore, mapState } from "vuex";
export default function useState(mapper) {
const store = useStore();
const stateFnsObj = mapState(mapper);//拿到的是mapState获取的函数
const newState = {};
Object.keys(stateFnsObj).forEach(key => {
// 获取obj里面所有的key
newState[key] = computed(stateFnsObj[key].bind({ $store: store }))
})
return newState;
}
使用如下:
<template>
<div class="home">
<!--4.setup计算属性,封装useState方法 -->
<h2>useState-name{{ name }}</h2>
<h2>level:{{ level }}</h2>
</div>
</template>
<script setup>
import useState from '@/hooks/userState'
// 使用useState
const { name, level } = useState(['name', 'level'])
</script>
- (推荐)方法三:直接解构store.state
<template>
<div class="home">
<!--4.setup计算属性,封装useState方法 -->
<h2>useState-name{{ name }}</h2>
<h2>level:{{ level }}</h2>
</div>
</template>
<script setup>
import { useStore } from 'vuex';
import { toRefs} from 'vue';
// 3.直接对store.state进行结构
const store = useStore();
const { name, level } = toRefs(store.state)
</script>
4. 核心概念二getter
4.1. 基本使用$store.getters.数据名
- 直接使用
注: reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。 reduce() 可以作为一个高阶函数用于函数的 compose。
- 引入别的getters
- 返回函数, 接收参数。 getters中的函数本身,可以返回一个函数,那么在使用的地方相当于可以调用这个函数:
4.2. 映射使用mapGetters
1.options api
2. composition api
法一:mapGetters,使用bind绑定$store
法二:直接解构,包裹toRefs
法三:单独使用computed
const message = computed(() => store.getters.message)
5. 核心概念三Mutations
5.1. 重要原则
- 修改state, 必须使用mutation
5.2. 基本使用this.$store.commit("函数名")
- 直接使用
- option api
2. 传入参数:payload为对象类型
5.3. 映射使用mapMutations
我们也可以借助于辅助函数,帮助我们快速映射到对应的方法中:
1.option api
2. compisition api
5.4. 重要原则
- 一条重要的原则就是要记住 mutation 必须是同步函数
- 这是因为devtool工具会记录mutation的日记;
- 每一条mutation被记录, devtools都需要捕捉到前一状态和后一状态的快照;
- 但是在mutation中执行异步操作,就无法追踪到数据的变化;
6. 核心概念四Actions
6.1 action的基本使用
- Action类似于mutation,不同在于:
- Action提交的是mutation,而不是直接变更状态;
- Action可以包含任意异步操作;
- 这里有一个非常重要的参数context:
- context是一个和store实例均有相同方法和属性的context对象;
- 所以我们可以从其中获取到commit方法来提交一个mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters;
6.2 mapActions的辅助函数
- action也有对应的辅助函数:
- 对象类型的写法;
- 数组类型的写法;
1.option API中使用
import { mapActions } from 'vuex';
export default {
methods: {
...mapActions(['incrementAction', 'changeNameAnction'])
}
}
2.compisition API使用
<script setup>
import { useStore, mapActions } from 'vuex';
const store = useStore();
const actions = mapActions(['incrementAction', 'changeNameAnction']);
const newActions = {}
Object.keys(actions).forEach(key => {
newActions[key] = actions[key].bind({ $store: store })
})
const { incrementAction, changeNameAnction } = newActions;
</script>
使用默认函数写法:
function increment(){
store.dispatch("incrment")
}
6.3 actions中进行异步操作
- 发送网络请求
async fetchHomeMultidataAction() {
// 1. 返回promise,给promise设置then
fetch("http://123.207.32.32:8000/home/multidata").then(res => {
res.json().then(data => {
console.log(data);
})
})
// 2 .promise的链式调用
fetch("http://123.207.32.32:8000/home/multidata").then(res => {
return res.json()
}).then(data => {
console.log(data);
})
// 3.async,await
const res = await fetch("http://123.207.32.32:8000/home/multidata")
const data = await res.json()
console.log(data);
}
}
- 数据请求的过程
- promise
换成
resolve(data.data);
7. 核心概念五Modules
7.1. 什么是Module?
- 由于使用单一状态树,应用的所有状态会集中到一个比较大的对象,当应用变得非常复杂时, store 对象就有可能变得相当臃肿;
- 为了解决以上问题, Vuex 允许我们将 store 分割成模块(module) ;
- 每个模块拥有自己的 state、 mutation、 action、 getter、甚至是嵌套子模块;
7.2. modules的基本使用
- 使用state时,需要通过state.modulesName.XXX
7.3. 子模块getters的使用
- 使用getters时,是直接getters.XXX
7.4. 派发事件时,不需要分模块的。提交commit时,默认也是不需要更模块名称
<script setup>
import { useStore } from 'vuex';
// 告诉vuex发送网络请求
const store = useStore();
store.dispatch('fetchHomeMultidataAction')
</script>
7.5. module的命名空间
- 默认情况下,模块内部的action和mutation仍然是注册在全局的命名空间中的:
- 这样使得多个模块能够对同一个 action 或 mutation 作出响应;
- Getter 同样也默认注册在全局命名空间;
- 如果我们希望模块具有更高的封装度和复用性,可以添加
namespaced: true的方式使其成为带命名空间的模块:
- 当模块被注册后,它的所有 getter、 action 及 mutation 都会自动根据模块注册的路径调整命名
3. 加上命名空间后,获取getters方式改变
import { useStore } from 'vuex';
// 告诉vuex发送网络请求
const store = useStore();
//store.dispatch('fetchHomeMultidataAction')
//加上命名空间后
store.dispatch('home/fetchHomeMultidataAction')
</script>
7.6.module修改或派发根组件
果我们希望在action中修改root中的state,那么有如下的方式: