说一下 vm.$set 原理

60 阅读1分钟

vm.$set(或全局的Vue.set)是 Vue.js 为解决响应式系统动态添加属性限制而提供的关键 API。为了让你快速把握全局,下表汇总了其核心原理与对比。

关键方面​直接赋值​​使用 vm.$set
​响应式能力​新属性​​非响应式​​,数据变化视图不更新新属性​​是响应式​​的,数据变化触发视图更新
​实现机制​普通对象属性赋值,Vue 无法拦截内部调用 Object.defineProperty/ splice,并触发依赖通知
​触发更新​不会自动触发视图更新​自动触发​​视图更新
​适用场景​静态数据,无需响应式更新​动态添加​​需要响应式追踪的属性

🔧 核心原理:如何实现动态响应式

vm.$set的本质是 ​​Vue 响应式系统的一个“补丁”机制​​。由于 Vue 2 基于 Object.defineProperty实现响应式,它只能在组件初始化时递归遍历并劫持 data 对象上​​已存在​​的属性。对于后来动态添加的属性,Vue 没有机会为其设置 getter/setter,因此无法追踪变化。 vm.$set的工作原理正是为了弥补这一限制,其内部逻辑主要针对两种数据类型进行处理:

  1. ​处理数组​​ 当目标 target是数组时,vm.$set内部会调用数组的 splice方法。Vue 已经重写了数组的变异方法(如 push, pop, splice等),这些方法在执行时会自动触发视图更新。因此,通过 splice插入新元素,能确保新元素被 Vue 侦测到并成为响应式的。
  2. ​处理对象​​ 对于对象,vm.$set的执行流程更为精细,其决策过程如下图所示:

image.png

flowchart TD
    A[调用 vm.$settarget, key, value] --> B{target 是否为数组?}
    B -- 是 --> C[使用 splice 方法插入值]
    B -- 否 --> D{key 是否已存在于 target 中?}
    D -- 是 --> E[直接赋值<br>因该属性已是响应式]
    D -- 否 --> F{target 是否为响应式对象?}
    F -- 否 --> G[直接赋值<br>因无需响应式处理]
    F -- 是 --> H[调用 defineReactive 方法<br>将新属性定义为响应式]
    H --> I[调用 ob.dep.notify<br>触发依赖更新]
    I --> J[视图更新]

从上图可以看出,最关键的一步是当​​为响应式对象添加新属性​​时,vm.$set会调用 defineReactive函数。这个函数是 Vue 响应式系统的核心,它利用 Object.defineProperty为属性 key添加 getter 和 setter,使其变得可追踪。完成后,它会通过 ob.dep.notify()通知所有依赖于此对象的“订阅者”(如组件的渲染函数)进行更新,从而立即触发视图刷新。

💡 主要用途与典型场景

  • ​动态扩展响应式对象​​:从 API 接口获取数据后,需要为现有对象添加新的响应式属性。
  • ​初始化未声明的属性​​:在复杂表单或交互中,根据用户操作动态添加数据字段。

⚠️ 重要注意事项

  • ​目标限制​​:vm.$settarget参数不能是 Vue 实例本身(如 this)或 Vue 实例的根数据对象(如 this.$data)。
  • ​性能考量​​:虽然 vm.$set很强大,但频繁使用可能会触发多次渲染更新。对于已知的数据结构,应尽量在 data选项中预先声明所有属性,这是最佳实践。
  • ​Vue 3 的改进​​:在 Vue 3 中,由于使用 Proxy实现响应式,Proxy可以代理整个对象,能天然拦截包括属性添加在内的各种操作。因此,在 Vue 3 中直接为响应式对象添加新属性也是响应式的,通常不再需要 vm.$set等价方法。

希望以上解释能帮助你透彻理解 vm.$set的原理和作用。