Vue | Vue 技术探索:深入理解 Vuex

一、关于 Vuex

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

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

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

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

二、核心概念

State(状态)

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

Getters(派生状态)

Getter 是 Vuex 中的计算属性,它允许你从 store 中的 state 派生出一些状态,类似于 Vue 组件中的计算属性。

Mutations(变更)

Mutations 是唯一能够更改 Vuex state 的方法。每个 Mutation 都有一个字符串的事件类型和一个回调函数,这个回调函数就是实际进行状态更改的地方。

Mutation 必须是同步函数

Actions(动作)

Action 提交的是 Mutation,而不是直接变更状态,可以包含任意异步操作。

Action 可以包含任意的异步操作,比如向服务器发送请求。

Modules(模块)

由于使用单一状态树,所有状态会集中到一个比较大的对象,当应用变得非常复杂时,store 对象会变得相当臃肿。为了解决这个问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutations、actions、getters,甚至是嵌套子模块。

三、使用说明

项目中创建 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,开发者可以更清晰地传达这些函数的特殊角色和用途,从而提高代码的可读性和维护性。这种命名约定是编程中的一种最佳实践,帮助开发团队保持代码的一致性和清晰性。

小结:Vuex中的mutations函数命名建议使用大写‌,主要是出于以下几个原因:

  1. 一致性‌:Vuex的设计原则之一是保持一致性,使用大写命名可以清晰地表明这些函数是专门用于修改状态的,与普通的JavaScript函数形成区分。

  2. 可读性‌:大写命名有助于提高代码的可读性,使得开发者在阅读代码时能够快速识别出哪些函数是用于触发状态变更的。

  3. 约定俗成‌:在Vuex社区中,已经形成了一种约定,即mutations的方法名应该全部大写。这种约定有助于维护代码的一致性和可维护性。

  4. 避免与普通方法混淆‌:通过大写命名,可以避免mutations与方法(如actions)之间的混淆,确保开发者在使用时更加明确其意图和作用。

综上所述,虽然Vuex并没有严格规定mutations的命名必须全部大写,但为了遵循最佳实践、提高代码的可读性和可维护性,以及与Vuex社区的广泛实践保持一致,建议开发者在定义mutations时使用大写命名‌

面试官: Object.create(null) 与 {} 的区别

①原型链:

  • {} 创建的对象的原型是 Object.prototype,因此它继承了 Object.prototype 上的属性和方法。
  • Object.create(null) 创建的对象没有原型链,因此不继承任何属性和方法,是一个纯净的空对象。

②属性访问权限:

  • {} 创建的对象的属性访问权限是正常的,可以访问和修改对象的属性,也可以使用原型链上的属性和方法。
  • Object.create(null) 创建的对象是一个空对象,没有原型链,因此其属性访问权限更加纯净,不会受到原型链上属性的影响。

小结:Object.create(null)‌Object.create(null)与‌{}‌的主要区别在于它们的原型链和功能。

  • Object.create(null)‌ 创建一个没有任何原型的方法。这意味着使用Object.create(null)创建的对象没有原型,也就是说它们不会继承任何内置对象(如Object)的属性或方法。这样的对象非常纯净,因为它们不从任何原型链上继承任何方法或属性,这样确保了对象的所有方法和属性都是明确添加到对象上的,而不是从原型链上继承来的。这种特性使得Object.create(null)在需要避免原型链干扰的场景中非常有用,例如,当需要创建一个对象,但又不希望这个对象继承任何内置对象的属性或方法时‌12。

  • {}‌ 实际上是通过‌new Object()‌创建的,这样的对象具有原型,并且默认会继承Object对象的所有属性。这意味着,即使你创建一个空对象字面量({}),它仍然有一个原型,即Object.prototype,并且可以通过原型链访问到Object对象上的所有方法和属性。因此,使用字面量创建的对象默认具有一些内置的方法和属性,这些方法和属性可能在你不需要的情况下被调用‌34。

综上所述,如果你需要一个非常纯净的对象,即一个没有任何内置方法或属性的对象,应该使用Object.create(null)。这种方式创建的对象不会受到原型链上任何方法的干扰,确保了对象的所有属性和方法都是明确添加的,而不是从原型链上继承的。而使用字面量({})创建的对象则默认继承了Object对象的属性和方法,可能会在某些情况下导致意外的行为‌。

面试官: Vuex 命名空间重名会怎么样?

  • 会抛出异常警告, 后注册的模块会覆盖先注册的模块,导致前一个模块的状态被后一个模块的状态所替代,因此会出现状态覆盖的情况。这可能会导致应用中的某些模块无法正常工作,或者产生意外的行为。因此,在使用 Vuex 时,应该避免命名空间重名,以确保状态管理的可预测性和稳定性。

小结:Vuex 的命名空间重名会导致getteraction的冲突,从而引发错误。

在 Vuex 中,命名空间是一种组织store状态树的方式,它允许你将相关的状态分割到不同的模块中,并通过命名空间来区分。这种机制有助于保持状态的局部性和可维护性。然而,如果在不同的模块中定义了同名的 getter 或 action,就会导致冲突。例如,如果两个模块都定义了一个名为 list 的 getter,那么在尝试访问这个 getter 时,Vuex 无法确定应该使用哪个模块中的 list getter,从而导致错误。

为了避免这种情况,你需要确保每个模块中的 getter 和 action 都有唯一的名字,或者通过配置命名空间来避免重名。当启用命名空间后,getter 和 action 会收到局部化的 gettersdispatch 和 commit,这样你就可以在不同的模块中使用相同的名字而不会发生冲突。此外,如果你需要在带命名空间的模块内访问全局状态或 getter,可以通过传递的 rootState 和 rootGetters 参数来访问全局资源。

总的来说,为了避免 Vuex 中的命名空间重名问题,你应该:

  1. 确保每个模块中的 getter 和 action 都有唯一的名字。
  2. 使用命名空间来组织模块,以避免全局命名冲突。
  3. 如果需要,通过传递的参数访问全局状态和 getter‌

Vuex 命名空间重名‌指的是在 Vuex 的状态管理中,当多个模块或子模块使用相同的命名空间时,可能会发生名称冲突的情况。在 Vuex 中,命名空间是一种组织状态管理的方式,它允许你将状态分割成更小的、可管理的部分,并通过命名空间来区分它们。这样做的好处是能够避免全局状态的命名冲突,使得代码更加清晰和可维护。然而,当不同的模块或子模块不小心使用了相同的命名空间时,就可能导致重名问题,进而影响到状态的正确访问和更新。

为了避免命名空间重名的问题,开发者需要仔细规划命名空间的命名规则,确保每个模块或子模块的命名空间是唯一的,或者至少是能够明确区分开的。此外,Vuex 还提供了创建基于某个命名空间的辅助函数(如 createNamespacedHelpers),这些函数可以帮助开发者更好地管理命名空间,减少重名冲突的可能性。

总的来说,Vuex 命名空间重名是指在使用 Vuex 进行状态管理时,由于命名空间的设置不当或规划不周,导致不同的模块或子模块使用了相同的命名空间名称,从而引发的问题。通过合理的命名规划和利用 Vuex 提供的工具和方法,可以有效避免这种情况的发生‌

参考:Vuex模块化以及命名空间namespaced的使用_vuex 命名空间-优快云博客

面试官: Vuex 的核心概念是什么?

  • State、Mutation、Action、Getter、Module。

面试官: Vuex 是如何实现响应式的?

  • Vue.set

 Vuex 使用了 Vue 的响应式系统来保证状态的响应式。在 Vuex 中,状态存储于 state 对象中,这个对象在 Vuex 的 store 中被代理,使得它可以作为响应式系统的一部分工作。

当你使用 Vue.set 函数来更新 Vuex store 中的状态时,Vue 会自动追踪依赖并更新相关的视图。这是因为 Vuex 在初始化 store 时,会将 state 对象转换为响应式对象。

以下是一个简单的例子,展示如何在 Vuex store 中使用 Vue.set:

// 假设有一个 Vuex store
import Vue from 'vue';
import Vuex from 'vuex';
 
Vue.use(Vuex);
 
const store = new Vuex.Store({
  state: {
    obj: {
      key1: 'value1'
    }
  },
  mutations: {
    updateProperty(state, newValue) {
      // 使用 Vue.set 更新 obj 对象的 key1 属性
      Vue.set(state.obj, 'key1', newValue);
    }
  }
});
 
// 调用 mutation 来更新状态
store.commit('updateProperty', 'newValue');
 
// 现在,当你访问 store.state.obj.key1,它将返回 'newValue',并且视图会自动更新。

在这个例子中,当我们调用 updateProperty mutation 并传递新值 'newValue' 时,Vuex store 会使用 Vue.set 来确保 state.obj.key1 是响应式的,并且任何依赖这个属性的组件都会被正确地通知并更新。 

面试官: 如何在 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 将异步转为同步

参照资料

—— Vue | Vuex中五大属性及其辅助函数的使用说明-儒雅烤地瓜的优快云博客 ——

—— Vue | Vuex模块化及命名空间namespaced的使用_儒雅烤地瓜的优快云博客 ——

Vuex详解,一文彻底搞懂Vuex_烤地瓜优快云 | Vue状态管理工具Vuex工作原理解析_脚本之家

前端面试必备:Vuex的简单使用及原理分析 | Vue状态管理工具-Vuex 的实现原理-优快云博客

Vue状态管理工具-Vuex 实现原理 - 简书 |深入了解vuex的实现原理-Vue.js-PHP中文网

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

儒雅的烤地瓜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值