在 Vue 中,如果在运行时动态给 data
添加新的属性,Vue 无法自动追踪这个新属性的变化。这是因为 Vue 使用的是 Object.defineProperty
进行数据劫持,而 Object.defineProperty
只能在对象初始化时对已有的属性进行监听,因此动态添加的属性不会被 Vue 侦测到,也不会触发视图的更新。
发生什么?
假设你在一个组件的 data
中没有定义 newProperty
,然后你想动态添加它:
this.$data.newProperty = 'newValue';
console.log(this.newProperty); // 'newValue'
在上面的例子中,你虽然可以通过 $data
访问到 newProperty
,但是 Vue 无法响应它的变化,界面也不会更新。
解决办法:
Vue 提供了两种方法解决这个问题:
- 使用
Vue.set()
方法:可以动态地将属性添加到响应式对象中,并确保它是响应式的。
Vue.set(this.someObject, 'newProperty', 'newValue');
this.$set()
方法:与Vue.set()
类似,适用于实例内的响应式数据。
this.$set(this.someObject, 'newProperty', 'newValue');
示例:
data() {
return {
person: {
name: 'John'
}
};
},
mounted() {
// 动态添加一个属性,Vue 无法追踪
this.person.age = 25;
// 正确的方式,Vue 会追踪到新属性的变化
this.$set(this.person, 'age', 25);
}
Vue 源码分析:
在 Vue 源码中,Vue 初始化响应式数据时,会通过 initData()
函数遍历 data
对象上的属性,并调用 defineReactive()
方法。这个方法使用 Object.defineProperty
将属性定义为 getter 和 setter,从而使得 Vue 可以劫持数据的读取和更新。
然而,Object.defineProperty
不能对新增的属性进行监听。这就是为什么在运行时动态添加属性不会触发响应的原因。因此,Vue 提供了 set
方法,它使用 defineReactive
手动为新增属性设置响应式劫持。
场景:
动态给 data
添加属性常见于异步请求完成后,或者组件渲染过程中,需要给对象动态添加数据时。比如,一个用户表单可能开始时只有 name
和 email
字段,但在某些交互下需要动态添加 phone
字段。