2.16Vue全家桶-Vuex状态管理
1.认识应用状态管理
1.1什么是状态管理
在开发中,我们的应用程序需要处理各种各样的数据,这些数据需要保存在我们应用程序中的某一个位置,对于这些数据的管理我们就称之为是状态管理。
在前面我们是如何管理自己的状态呢?
- 在 Vue 开发中,我们使用组件化的开发方式;
- 而在组件中我们定义 data 或者在 setup 中返回使用的数据,这些数据我们称之为 state;
- 在模块 template 中我们可以使用这些数据,模块最终会被渲染成 DOM,我们称之为 View;
- 在模块中我们会产生一些行为事件,处理这些行为事件时,有可能会修改 state,这些行为事件我们称之为 actions;
1.2复杂的状态管理
JavaScript 开发的应用程序,已经变得越来越复杂了:
- JavaScript 需要管理的状态越来越多,越来越复杂;
- 这些状态包括服务器返回的数据、缓存数据、用户操作产生的数据等等;
- 也包括一些 UI 的状态,比如某些元素是否被选中,是否显示加载动效,当前分页;
当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:
- 多个视图依赖于同一状态;
- 来自不同视图的行为需要变更同一状态;
我们是否可以通过组件数据的传递来完成呢?
- 对于一些简单的状态,确实可以通过 props 的传递或者 Provide 的方式来共享状态;
- 但是对于复杂的状态管理来说,显然单纯通过传递和共享的方式是不足以解决问题的,比如兄弟组件如何共享数据呢?
1.3Vuex的状态管理
管理不断变化的 state 本身是非常困难的:
- 状态之间相互会存在依赖,一个状态的变化会引起另一个状态的变化,View 页面也有可能会引起状态的变化;
- 当应用程序复杂时,state 在什么时候,因为什么原因而发生了变化,发生了怎么样的变化,会变得非常难以控制和追踪;
因此,我们是否可以考虑将组件的内部状态抽离出来,以一个全局单例的方式来管理呢?
- 在这种模式下,我们的组件树构成了一个巨大的 “视图 View”;
- 不管在树的哪个位置,任何组件都能获取状态或者触发行为;
- 通过定义和隔离状态管理中的各个概念,并通过强制性的规则来维护视图和状态间的独立性,我们的代码会变得更加结构化和易于维护、跟踪;
这就是 Vuex 背后的基本思想,它借鉴了 Flux、Redux、Elm(纯函数语言,redux 有借鉴它的思想);
当然,目前 Vue 官方也在推荐使用 Pinia 进行状态管理,我们后续也会进行学习。
2.Vuex的基本使用
2.1Vuex的手动安装
2.2Vuex的基本使用过程
第一,在文件夹store中创建一个index.js的文件。
第二,1.引入vuex ,2.定义一些公有的状态,3.导出
第三,在main.js中app.use()
第四,直接在组件中进行使用(可以在模板中使用,在js代码中使用)
2.3创建store
- Vuex 的核心 - store:
每一个 Vuex 应用的核心是 store(仓库),本质上是一个容器,包含应用中大部分的状态(state)。
Vuex 与单纯全局对象的区别:
响应式存储:Vuex 的状态存储是响应式的,当 Vue 组件从 store 中读取状态时,若 store 中的状态发生变化,相应的组件也会被更新。
状态改变方式:不能直接改变 store 中的状态,改变 store 中状态的唯一途径是提交(commit)mutation,这样便于跟踪每一个状态的变化,通过工具更好地管理应用的状态。
- 使用步骤:
- 创建 Store 对象。
- 在 app 中通过插件安装。
2.4使用store
3.mapStates映射
3.1在optionAPI中进行状态映射
- 在组件中获取状态的优化方式:
- 若觉得之前在组件中获取状态的方式繁琐(表达式过长),可使用计算属性。示例代码如下:
javascript
运行
computed: {counter() {return this.$store.state.counter} },
- 若有多个状态需要获取,可以使用
mapState
的辅助函数。
mapState
的方式一:对象类型。mapState
的方式二:数组类型。- 也可以使用展开运算符和原来的
computed
混合在一起。
3.2在compositionAPI中进行状态映射
- 在
setup
中获取状态:
- 单个获取状态很简单,通过
useStore
拿到store
后去获取某个状态即可。- 若需使用
mapState
功能,默认情况下 Vuex 未提供非常方便的使用方式,因此进行了函数封装。- 封装函数代码:
javascript
运行
import { useStore, mapState } from 'vuex'; import { computed } from 'vue';export function useState(mapper) {const store = useStore();const stateFns = mapState(mapper)const state = {}Object.keys(stateFns).forEach(fnKey => {state[fnKey] = computed(stateFns[fnKey].bind({ $store: store }))})return state }
- 在
setup
中使用封装函数示例:javascript
运行
setup() {const state = useState({name: state => state.name,age: state => state.age,height: state => state.height})return {...state} }
3.核心概念State
见2、3
4.核心概念Getters
4.1Getters的基本使用
- Vuex 中 getters 的用途:当某些属性需要经过变化后使用时,可以使用 getters。
- 代码示例:
javascript
运行
const store = createStore({state() {return {counter: 0,name: "coderwhy",age: 18,height: 1.88,books: [{ name: "vuejs", count: 2, price: 110 },{ name: "react", count: 3, price: 120 },{ name: "webpack", count: 4, price: 130 }]}},getters: {totalPrice(state) {let totalPrice = 0;for (const book of state.books) {totalPrice += book.count * book.price}return totalPrice}} })
- 在模板中使用 getters 的示例:
html
预览
<div><h2>{{ $store.getters.totalPrice }}</h2> </div>
4.2Getters的mapGetters映射
4.3Getters第二个参数
4.4getters的返回值
在optionAPI中
在compositionAPI中
5.核心概念Mutations
5.0Mutations的基本使用
5.1Mutations携带参数
很多时候我们在提交 mutation 的时候,会携带一些数据,这个时候我们可以使用参数:
plaintext
mutations: {addNumber(state, payload) {state.counter += payload} }
payload 为对象类型
plaintext
addNumber(state, payload) {state.counter += payload.count }
对象风格的提交方式
plaintext
$store.commit({type: "addNumber",count: 100 })
5.2Mutations常量类型
定义常量:mutation - type.js
js
export const ADD_NUMBER = 'ADD_NUMBER'
定义 mutation
js
[ADD_NUMBER](state, payload) {state.counter += payload.count }
提交 mutation
js
$store.commit({type: ADD_NUMBER,count: 100 })
5.3Mutations的mapMutations映射
我们也可以借助辅助函数,帮我们快速映射到对应的方法中:
js
methods: {...mapMutations({addNumber: ADD_NUMBER,}),...mapMutations(["increment", "decrement"]), }
在 setup 中使用也是一样的:
js
const mutations = mapMutations(['increment', 'decrement']); const mutations2 = mapMutations({addNumber: ADD_NUMBER })
5.4Mutations的重要原则
一条重要的原则就是要记住 mutation 必须是同步函数:
- 这是因为 devtool 工具会记录 mutation 的日记;
- 每一条 mutation 被记录,devtools 都需要捕捉到前一状态和后一状态的快照;
- 但是在 mutation 中执行异步操作,就无法追踪到数据的变化;
所以 Vuex 的重要原则中要求 mutation 必须是同步函数;
但是如果我们希望在 Vuex 中发送网络请求的话需要如何操作呢?
6.核心概念Actions
6.1Actions的基本使用
Action 类似于 mutation,不同在于:
- Action 提交的是 mutation,而不是直接变更状态;
- Action 可以包含任意异步操作;
这里有一个非常重要的参数 context:
- context 是一个和 store 实例均有相同方法和属性的 context 对象;
- 所以我们可以从其中获取到 commit 方法来提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters;
但是为什么它不是 store 对象呢?这个等到我们讲 Modules 时再具体来说;
代码块:
javascript
mutations: {increment(state) {state.counter++} }, actions: {increment(context) {context.commit("increment")} }
6.2Actions的分发操作
如何使用 action 呢?进行 action 的分发:
- 分发使用的是 store 上的 dispatch 函数;
代码块 1:
javascript
运行
add() {this.$store.dispatch("increment"); }
同样的,它也可以携带我们的参数:
代码块 2:
javascript
运行
add() {this.$store.dispatch("increment", { count: 100 }); }
也可以以对象的形式进行分发:
代码块 3:
javascript
运行
add() {this.$store.dispatch({type: "increment",count: 100,}); }
6.3Actions的mapActions的映射
action 也有对应的辅助函数:
- 对象类型的写法;
- 数组类型的写法;
代码块 1(数组类型写法 - 在 methods 中):
javascript
运行
methods: {...mapActions(["increment", "decrement"]),...mapActions({add: "increment",sub: "decrement"}) }
代码块 2(对象类型写法):
javascript
运行
const actions1 = mapActions(["decrement"]); const actions2 = mapActions({add: "increment",sub: "decrement" });
6.4用Vuex管理前端异步请求后端数据
7.核心概念Modules
7.1Modules的基本使用
7.2Modules的局部状态
对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象:
javascript
运行
mutations: {changeName(state) {state.name = "coderwhy"} }, getters: {info(state, getters, rootState) {return `name:${state.name} age:${state.age} height:${state.height}`} }
javascript
运行
actions: {changeNameAction({state, commit, rootState}) {commit("changeName", "kobe")} }
7.3Moudules的命名空间问题
默认情况下,模块内部的 action 和 mutation 仍然是注册在全局的命名空间中的:
- 这样使得多个模块能够对同一个 action 或 mutation 作出响应;
- Getter 同样也默认注册在全局命名空间;
如果我们希望模块具有更高的封装度和复用性,可以添加
namespaced: true
的方式使其成为带命名空间的模块:
- 当模块被注册后,它所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名;
javascript
运行
// 模块定义 const userModule = {namespaced: true,state: () => ({name: "why",age: 18,height: 1.8}),mutations: {changeName(state, payload) {state.name = "coderwhy"}},getters: {info(state, getters, rootState, rootGetters) {return `name:${state.name} age:${state.age} height:${state.height}`}},actions: {changeNameAction({commit, dispatch, state, rootState, getters, rootGetters}) {commit("changeName", "kobe")}} }