Vue 技术探索:深入理解 Vuex

432 阅读7分钟

文章主题: 全面掌握 Vuex 的使用和原理

一、关于 Vuex

Vuex 是一个专为 Vue.js 应用设计的状态管理模式(状态机) 。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

工作原理: 单向数据流、响应式更新、集中式存储。

应用场景: 管理大型应用的状态、方便的状态共享、调试工具支持。

流转顺序: 以命名空间作为索引的整体数据结构, 可以以独立入口(mutations)的方式对 state 进行读取和操作。actions -> mutations -> state

vuex v3源码地址: github.com/vuejs/vuex/…

二、核心概念

  1. State(状态)

    1. Vuex 使用一个单一对象存储所有的应用级别状态,所有组件共享同一个状态。这样可以更容易地追踪每一个状态的变化。
  2. Getter(派生状态)

    1. Getter 是 Vuex 中的计算属性,它允许你从 store 中的 state 派生出一些状态,类似于 Vue 组件中的计算属性。
  3. Mutation(变更)

    1. Mutation 是唯一能够更改 Vuex state 的方法。每个 Mutation 都有一个字符串的事件类型和一个回调函数,这个回调函数就是实际进行状态更改的地方。
    2. Mutation 必须是同步函数。
  4. Action(动作)

    1. Action 提交的是 Mutation,而不是直接变更状态,可以包含任意异步操作。
    2. Action 可以包含任意的异步操作,比如向服务器发送请求。
  5. Module(模块)

    1. 由于使用单一状态树,所有状态会集中到一个比较大的对象,当应用变得非常复杂时,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_STATE mutation 负责将 state 重置为初始状态。
  • resetState action 负责触发 RESET_STATE mutation。
  • 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 将异步转为同步