vue知识点整理 - 02

1 阅读3分钟

7. nextTick的理解

7.1. Vue 的异步更新队列

  • 数据变化不会立即更新 DOM:当数据变化时,Vue 将组件更新推入一个队列,并在同一事件循环(Event Loop)中缓冲所有数据变更。
  • 合并重复操作:避免同一数据多次修改导致的重复渲染,提升性能。

7.2. nextTick 的作用

  • 延迟回调执行:将回调函数推迟到下一个 DOM 更新周期之后执行,确保能访问到更新后的 DOM。

7.3. 实现机制

  • 底层基于事件循环:Vue 内部优先使用微任务(Microtask):
    • 支持环境:Promise.thenMutationObserversetImmediatesetTimeout(降级方案)
  • 保证执行时机:在浏览器完成 DOM 更新后触发回调。

8. 你知道v-model的原理吗?说说看

8.1. 表单元素中的 v-model

8.1.1. 基础实现,input标签

<input v-model="message">

等价于

<input 
  :value="message" 
  @input="message = $event.target.value"
>

原理

  • 属性绑定:将数据 message 绑定到 value 属性。
  • 事件监听:监听 input 事件,更新数据到 message

8.1.2. 其他表单元素的适配

  • <textarea>:与 <input> 相同。
  • <select>:监听 change 事件。
  • 复选框(Checkbox)和单选框(Radio):处理 checked 属性和 change 事件。

8.2. 组件中的 v-model

8.2.1. vue2

默认行为

  • Propvalue
  • 事件input
<!-- 父组件 -->
<ChildComponent v-model="message" />

等价于

<ChildComponent 
  :value="message" 
  @input="message = $event"
/>

自定义修改
通过 model 选项可修改默认的 prop 和事件名:

// 子组件
export default {
  model: {
    prop: 'title',    // 修改 prop 名为 title
    event: 'change'   // 修改事件名为 change
  }
}

8.2.2. vue3

默认行为

  • PropmodelValue
  • 事件update:modelValue
<!-- 父组件 -->
<ChildComponent v-model="message" />

等价于

<ChildComponent 
  :modelValue="message" 
  @update:modelValue="message = $event"
/>

8.3. 底层实现

8.3.1. 1. 数据 → 视图(响应式更新)

  • 依赖追踪:通过响应式系统(如 Object.definePropertyProxy),当数据变化时,触发组件的重新渲染。
  • 虚拟 DOM 对比:生成新的虚拟 DOM 并与旧 DOM 对比,更新变化的部分。

8.3.2. 2. 视图 → 数据(事件触发)

  • 监听表单输入事件(如 inputchange)。
  • 在事件回调中更新数据,触发响应式系统的依赖通知。
数据变化 → 更新视图(响应式系统)
视图输入 → 触发事件 → 更新数据(事件监听)

9. Vue是怎样依赖收集的?

9.1. 核心概念

  1. 响应式对象
    通过 Object.defineProperty (Vue 2) 或 Proxy (Vue 3) 劫持数据访问,将普通对象转为响应式对象。
  2. 依赖(Dep)
    每个响应式属性对应一个 Dep 实例,用于管理所有依赖该属性的观察者(Watcher)。
  3. 观察者(Watcher)
    表示一个依赖关系,如组件渲染函数、计算属性或用户自定义的 watch。当数据变化时,Watcher 负责执行更新。

9.2. 依赖收集流程

9.2.1. Vue 2 的依赖收集(基于 Object.defineProperty)

    • 步骤
      1. 初始化响应式数据
        递归遍历对象,为每个属性创建 Dep 并劫持 getter/setter
function defineReactive(obj, key) {
  const dep = new Dep();  // 每个属性一个 Dep
  let value = obj[key];
  Object.defineProperty(obj, key, {
    get() {
      if (Dep.target) {  // 当前正在计算的 Watcher
        dep.depend();   // 将 Watcher 添加到 Dep
      }
      return value;
    },
    set(newVal) {
      value = newVal;
      dep.notify();      // 通知所有 Watcher 更新
    }
  });
}
      1. 组件初始化时创建 Watcher
        每个组件实例对应一个渲染 Watcher,在 mount 阶段首次执行渲染函数。
      2. 触发依赖收集
        渲染过程中访问数据属性,触发 getter,将当前 Watcher 收集到属性的 Dep 中。
    • 依赖关系示例
// 数据
const data = { count: 0 };
// 转为响应式
observe(data);

// 组件 Watcher
new Watcher(() => {
  console.log(data.count);  // 访问 count,触发 getter
});

// 输出:0
data.count = 1;  // 触发 setter,Watcher 重新执行

9.2.2. Vue 3 的依赖收集(基于 Proxy)

  • 步骤
    1. 创建响应式对象
      使用 Proxy 拦截对象操作,无需递归初始化。
function reactive(obj) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      track(target, key);  // 依赖收集
      return Reflect.get(target, key, receiver);
    },
    set(target, key, value, receiver) {
      Reflect.set(target, key, value, receiver);
      trigger(target, key);  // 触发更新
    }
  });
}
    1. 全局依赖管理
      通过 track 函数将当前活动的 effect(类似 Watcher)记录到全局的 targetMap 中。
    2. 依赖触发
      数据修改时,trigger 函数从 targetMap 中找到依赖并执行。

9.3. 注意

9.3.1. Dep 与 Watcher 的多对多关系

  • Dep → Watcher:一个属性可被多个组件或计算属性依赖。
  • Watcher → Dep:一个 Watcher 可能依赖多个属性(如模板中使用多个数据)。

9.3.2. 2. 避免重复收集

  • Vue 2:通过 Dep.target 标识当前 Watcher,确保同一 Watcher 不会重复添加到 Dep
  • Vue 3:使用 activeEffecteffectStack 管理当前活动的副作用。

9.3.3. 3. 嵌套组件的依赖收集

  • 父组件渲染时遇到子组件,会先完成子组件的依赖收集,再回到父组件。

9.4. 依赖更新的触发

9.4.1. 同步更新流程

graph TD
  A[数据修改] --> B[触发 setter/Proxy set]
  B --> C[Dep.notify / trigger]
  C --> D[遍历所有 Watcher/Effect]
  D --> E[执行 Watcher.run / effect scheduler]
  E --> F[重新渲染或执行回调]

9.4.2. 异步更新流程

批量更新优化
多次数据修改会合并到一次更新中,避免重复渲染。

9.5. 特殊场景处理

9.5.1. 数组的依赖收集

  • Vue 2:重写数组方法(pushpop 等),在方法调用时手动触发通知。
  • Vue 3Proxy 直接监听数组索引变化和方法调用。

9.5.2. 2. 计算属性与侦听器

  • 计算属性
    惰性求值,只有当依赖变化时才重新计算。
  • 侦听器(watch)
    显式指定依赖源,变化时触发回调。

10. 计算属性computed和watch的区别是什么?

  1. 计算属性(computed)
    • 用途:用于定义依赖于其他数据属性的计算值。
    • 特点
      • 缓存:只有在依赖的数据变化时,才会重新计算。
      • 自动更新:当依赖的数据变化时,计算属性会自动更新。
    • 适用场景:需要基于其他数据生成的属性,例如全名、总价等。
  1. 监视器(watch)
    • 用途:用于在数据变化时执行特定的操作。
    • 特点
      • 每次变化都会触发:无论数据是否变化,只要被监视的数据变化,就会执行回调函数。
      • 无缓存:不进行缓存,每次变化都会执行逻辑。
    • 适用场景:需要在数据变化时执行副作用操作,比如调用API、修改其他状态等。
  1. 主要区别
    • 缓存机制:计算属性有缓存,只有依赖变化时才重新计算;监视器每次变化都会执行。
    • 执行方式:计算属性是被动更新,依赖变化自动触发;监视器是主动执行,变化触发回调函数。
    • 应用场景:计算属性适合生成依赖属性,监视器适合执行复杂逻辑或副作用操作。

11. Vue中的computed如何监听数组的变化?

在 Vue 中,computed 属性可以用来监听数据的变化并根据依赖的数据进行计算。然而,默认情况下,computed 并不会直接监听数组的变化,除非数组引用发生变化(例如,整个数组被替换)。具体来说:

11.1. 数组的变化类型

  • 引用变化:如果数组的引用发生变化(例如,array = newArray),computed 会触发重新计算。
  • 元素变化:如果数组中的元素发生变化(例如,修改数组中的某个项),computed 默认不会触发重新计算

11.2. computed监听数组变化

11.2.1. 方法一:使用数组变更方法

Vue 提供了一些数组变更方法(如 pushpopshiftunshiftsplice 等),这些方法会触发 Vue 的响应式更新机制,从而让 computed 属性重新计算。

11.2.2. 使用 watch 监听数组变化

如果你需要在数组变化时执行更复杂的逻辑,可以使用 watch 来监听数组的变化。

11.2.3. 使用 Vue.set this.$set

如果你直接修改数组中的某个元素(而不是使用数组变更方法),可以使用 Vue.setthis.$set 来触发响应式更新。

12. 强制更新组件的方法有哪些?分别有何利弊?

12.1. this.$forceUpdate()

12.1.1. 原理

强制当前组件实例重新渲染,跳过虚拟 DOM 的 Diff 过程,直接调用 render 函数生成新 VNode 并更新 DOM。

12.1.2. 使用方式

// 选项式 API
this.$forceUpdate();

// 组合式 API(Vue 3)
import { getCurrentInstance } from 'vue';
const instance = getCurrentInstance();
instance.proxy.$forceUpdate();

12.1.3. 优点

  • 简单直接:无需修改数据或结构
  • 局部更新:仅影响当前组件及其子组件

12.1.4. 缺点

  • 违背响应式原则:掩盖数据未正确响应的设计问题
  • 性能损耗:强制全组件树重新渲染,可能引发不必要的子组件更新
  • 不解决根本问题:未修复数据响应性,后续更新仍需手动触发

12.2. 修改组件的 key

12.2.1. 原理

通过改变 key 值,使 Vue 认为组件已销毁并重新创建,触发完整生命周期。

12.2.2. 使用方式

// 修改 key 值强制重建组件
this.componentKey = Date.now(); // 或递增计数器

12.2.3. 优点

  • 彻底重置组件状态:适合需要完全重新初始化的场景
  • 兼容性强:适用于任何组件和复杂场景

12.2.4. 缺点

  • 性能开销大:销毁和重建组件成本高,尤其是复杂组件
  • 状态丢失:组件内部状态(如表单输入)会被重置
  • 设计异味:可能反映组件设计存在耦合问题

12.3. 利用 v-if 切换

12.3.1. 原理

通过 v-if="show" 控制组件挂载/卸载,切换时重新渲染。

12.3.2. 使用方式

// 先卸载再重新挂载
this.show = false;
this.$nextTick(() => {
  this.show = true;
});

12.3.3. 优点

  • 控制灵活:可精确控制重新渲染时机
  • 类似 ****key ****的效果:但保留父组件状态

12.3.4. 缺点

  • 代码冗余:需要手动处理状态保留逻辑
  • 闪烁问题:快速切换可能导致界面闪烁
  • 不适用于保持内部状态:与 key 类似,会丢失子组件状态

12.4. 强制更新依赖(响应式系统)

12.4.1. 原理

通过修改响应式数据的引用地址,触发依赖更新。

12.4.2. 使用方式

// 对象:替换整个对象
this.obj = { ...this.obj };

// 数组:返回新数组
this.arr = this.arr.slice();

12.4.3. 优点

  • 符合响应式规范:利用 Vue 的响应式机制
  • 精准更新:仅触发相关依赖的重新计算

12.4.4. 缺点

  • 仅适用于引用类型:基础数据类型无法使用
  • 可能引发副作用:若其他代码依赖原引用,可能导致意外行为

12.5. 使用 Vue.set / this.$set

12.5.1. 原理

显式声明新增属性为响应式,适用于动态添加未预先声明的属性。

12.5.2. 使用方式

// Vue 2
this.$set(this.obj, 'newKey', 'value');

// Vue 3(通常不需要,Proxy 自动检测)
import { set } from 'vue';
set(this.obj, 'newKey', 'value');

12.5.3. 优点

  • 修复数据响应性:根本解决视图不更新的问题
  • 官方推荐:符合 Vue 设计模式

12.5.4. 缺点

  • 局限性:仅解决新增属性/数组索引的响应性问题
  • 需明确问题根源:无法修复其他原因导致的更新失败

12.6. 总结与最佳实践

方法适用场景推荐指数注意事项
$forceUpdate()紧急修复、非响应式数据依赖⭐⭐避免滥用,排查数据问题
修改 key需要完全重置组件状态⭐⭐性能敏感场景慎用
v-if切换临时强制重建组件可能导致闪烁
强制更新依赖引用类型数据未触发更新⭐⭐⭐优先选择的响应式方案
Vue.set动态添加属性/数组项未响应⭐⭐⭐⭐Vue 2 必备,Vue 3 少用