文章主题: 全面掌握 Vuex 的使用和原理
一、关于 Vuex
Vuex 是一个专为 Vue.js 应用设计的状态管理模式(状态机) 。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
工作原理: 单向数据流、响应式更新、集中式存储。
应用场景: 管理大型应用的状态、方便的状态共享、调试工具支持。
流转顺序: 以命名空间作为索引的整体数据结构, 可以以独立入口(mutations)的方式对 state 进行读取和操作。actions -> mutations -> state
vuex v3源码地址: github.com/vuejs/vuex/…
二、核心概念
-
State(状态)
- Vuex 使用一个单一对象存储所有的应用级别状态,所有组件共享同一个状态。这样可以更容易地追踪每一个状态的变化。
-
Getter(派生状态)
- Getter 是 Vuex 中的计算属性,它允许你从 store 中的 state 派生出一些状态,类似于 Vue 组件中的计算属性。
-
Mutation(变更)
- Mutation 是唯一能够更改 Vuex state 的方法。每个 Mutation 都有一个字符串的事件类型和一个回调函数,这个回调函数就是实际进行状态更改的地方。
- Mutation 必须是同步函数。
-
Action(动作)
- Action 提交的是 Mutation,而不是直接变更状态,可以包含任意异步操作。
- Action 可以包含任意的异步操作,比如向服务器发送请求。
-
Module(模块)
- 由于使用单一状态树,所有状态会集中到一个比较大的对象,当应用变得非常复杂时,store 对象会变得相当臃肿。为了解决这个问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter,甚至是嵌套子模块。
三、使用说明
项目中创建 store 文件夹管理 vuex 相关文件。
main.js
// main.js
import store from './store'
new Vue({
store
})
store/index.js
声明状态机 → 安装插件 → 创建实例(单例) → state / actions / mutations/ getters / modules
// index.js
// 1. 声明状态机
import Vuex from 'vuex'
import Vue from 'vue'
// 1. Vue.use 安装 Vuex 插件
Vue.use(Vuex) // 状态机不能重复实例化, 单实例存在, 有且只能有一个。
// 2. 创建实例 => 单例
const store = new Vuex.Store({
// 衔接业务的行为 承上启下 状态机的变化
actions:{
// 触发 管理 异步操作
setNodeInfo({commit},info){
// 尽可能和谐的处理多个异步操作
// async await 异步变成同步
// 顺序 commit
commit('SET_NODE_INFO',{info})
}
},
// 被 actions 触发进而进行本地操作, 再进行同步状态流转
mutations:{
SET_NODE_INFO(state,{info}){
// 数据处理
state.nodeInfo = info
}
},
// 状态集合
state:{
// 全局状态
nodeInfo: {
name: 'xxx',
age: 30,
words: 'hello vuex'
}
}
})
export default store
组件中使用 vuex
取数据的三种方式: mapState([]) / mapState({ key:ArrowFunction }) / $store
<template>
<div>
{{nodeInfo}}
</div>
</template>
<script>
import {mapState} from 'vuex'
// 为什么 vscode 可以智能提示联想到actions?
export default {
computed:{
// 方式1
...mapState(['nodeInfo'])
// 方式2: 可以拿到所有state,做数据整合
...mapState({
nodeInfo: state => state.nodeInfo
})
// 方式3: 简单粗暴, 直接从 $store 中取数据
localNodeInfo(){
return this.$store.state.nodeInfo
}
}
}
</script>
<template>
<div>
<p>Count: {{ count }}</p>
<p>Double Count: {{ doubleCount }}</p>
<button @click="increment">Increment</button>
<button @click="incrementAsync">Increment Async</button>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
export default {
computed: {
// 使用辅助函数 mapGetters 获取 getter
...mapGetters([
'doubleCount'
]),
// 直接通过 $store.state 获取状态
count() {
return this.$store.state.counter.count;
}
},
methods: {
// 使用辅助函数 mapActions 触发 action
...mapActions([
'increment',
'incrementAsync'
]),
// 直接通过 $store.dispatch 触发异步 action
incrementAsync() {
this.$store.dispatch('incrementAsync');
}
}
};
</script>
{{ $store.state.name }} 为什么可以直接使用呢?
<template>
<div>
{{ $store.state.name}}
</div>
</template>
<script>
// 1. 拼装一个 store 类, 包含所有 store 实例本身具备的能力
// $store
get state(){
return this._vm.state.$state
}
// 2. mixin: vuex3 准备了一个全局混入 mixin
// beforeCreate 生命周期中 => 混入 Vue.mixin 混入了同一个 store 实例, 并且同时挂载在 $store 上
// 3. 实现响应式 - Vue.set
</script>
四、Vuex 3.x 重置 state 的最佳方案
在 Vuex 3.x 中,重置 state 的最佳方案通常是通过一个专门的 mutation 来实现。这个 mutation 负责将 state 的各个属性重置为初始值。以下是一个示例:
// 在 mutations 中定义一个重置 state 的 mutation
const mutations = {
RESET_STATE(state) {
Object.assign(state, getDefaultState());
}
};
// 在 getters 中定义一个获取初始 state 的方法
const getters = {
// ...
};
// 在 actions 中定义一个触发重置 state 的 action
const actions = {
resetState({ commit }) {
commit('RESET_STATE');
}
};
// 定义初始 state 的方法,用于在重置时获取初始 state
const getDefaultState = () => {
return {
// 初始状态
};
};
// 创建 Vuex store 实例
const store = new Vuex.Store({
state: getDefaultState(),
mutations,
actions,
getters
});
export default store;
RESET_STATEmutation 负责将 state 重置为初始状态。resetStateaction 负责触发RESET_STATEmutation。getDefaultState方法定义了初始 state,以便在重置时获取初始状态。- 在创建 Vuex store 实例时,初始 state 使用了
getDefaultState方法获取。
这样,当需要重置 state 时,只需要调用 resetState action 即可。这种做法保持了 store 的结构清晰,同时使得重置 state 操作更加方便和可控。
五、面试题
面试官: mutations 函数命名为什么要大写?
- 通过使用全大写命名 mutations,开发者可以更清晰地传达这些函数的特殊角色和用途,从而提高代码的可读性和维护性。这种命名约定是编程中的一种最佳实践,帮助开发团队保持代码的一致性和清晰性。
面试官: Object.create(null) 与 {} 的区别
原型链:
{}创建的对象的原型是Object.prototype,因此它继承了Object.prototype上的属性和方法。Object.create(null)创建的对象没有原型链,因此不继承任何属性和方法,是一个纯净的空对象。
属性访问权限:
{}创建的对象的属性访问权限是正常的,可以访问和修改对象的属性,也可以使用原型链上的属性和方法。Object.create(null)创建的对象是一个空对象,没有原型链,因此其属性访问权限更加纯净,不会受到原型链上属性的影响。
面试官: Vuex 命名空间重名会怎么样?
- 会抛出异常警告, 后注册的模块会覆盖先注册的模块,导致前一个模块的状态被后一个模块的状态所替代,因此会出现状态覆盖的情况。这可能会导致应用中的某些模块无法正常工作,或者产生意外的行为。因此,在使用 Vuex 时,应该避免命名空间重名,以确保状态管理的可预测性和稳定性。
面试官: Vuex 的核心概念是什么?
- State、Mutation、Action、Getter。
面试官: Vuex 是如何实现响应式的?
- Vue.set
面试官: 如何在 Vuex 中实现持久化存储?
- localStorage / sessionStorage
- Vuex 插件(如 vuex-persistedstate)来将 store 中的状态持久化到本地存储中。
面试官: state 与 getter 的区别
- State 是存储应用状态的地方,而 Getter 是对状态的派生,用于获取、计算和返回状态。
面试官: Mutation 和 Action 有什么区别?
- Mutation 用于同步地修改状态,而 Action 用于异步地修改状态或者触发多个 Mutation。
面试官: 如何在 Vuex 中实现异步操作的串行执行?
- 使用 async/await 来处理异步操作,保证它们按照期望的顺序执行,或者在 Action 中通过 Promise 链的方式来实现。
面试官: 在 Vuex 中如何处理大量状态的性能问题?
- 可以使用计算属性来对状态进行筛选和缓存,避免在组件渲染过程中频繁地访问和计算大量状态。
面试官: Vuex 的性能优化策略有哪些?
- 包括使用计算属性、合理划分模块、异步操作的优化、持久化缓存、避免不必要的状态更新等。
面试官: 在 main.js 中,我们再次导入了 Vue 模块,这与在其他文件(如 index.js)中导入 Vue 使用 Vue.use(Vuex)是否会导致重复声明?能否解释这种导入方式的工作机制,以及为什么这种做法不会导致 Vue 实例的重复创建?
每个模块都可以独立地导入 Vue 并使用它,而不会重复创建 Vue 实例,因为 Vue 本身是一个单例模式,每个模块导入的都是同一个 Vue 实例。具体可以看一下 Vue 源码。
面试官: 跳过 mutation 行吗?
- 网络请求(fetch)下沉到 actions 中,网络请求是异步的, 返回时间无法确认, 直接修改数据, 会导致状态流转乱序。异步请求无法控制返回顺序。状态的流转非数据的改变。
- async/await + mutation 将异步转为同步