在 Vue2 开发中,经常会遇到这样一个问题:对象新增属性后,数据虽然更新了,但页面并没有随之更新。本文将通过一个例子来说明原因,并给出解决方案。
一、问题示例
我们先来看一个简单的例子:
ini
<div id="app">
<p v-for="(value, key) in item" :key="key">
{{ value }}
</p>
<button @click="addProperty">动态添加新属性</button>
</div>
Vue 实例代码如下:
javascript
const app = new Vue({
el: "#app",
data: () => ({
item: {
oldProperty: "旧属性"
}
}),
methods: {
addProperty() {
this.item.newProperty = "新属性"; // 动态添加新属性
console.log(this.item); // 数据确实更新了
}
}
});
点击按钮后,console
能打印出带有 newProperty
的对象,但页面上并没有新增一行。
二、原理分析
为什么会出现这种情况?
Vue2 的响应式系统是基于 Object.defineProperty
实现的。
javascript
const obj = {};
let val = "初始值";
Object.defineProperty(obj, "foo", {
get() {
console.log(`get foo: ${val}`);
return val;
},
set(newVal) {
if (newVal !== val) {
console.log(`set foo: ${newVal}`);
val = newVal;
}
}
});
当访问或修改 foo
时,会触发 getter/setter。但如果直接新增属性:
ini
obj.bar = "新属性";
此时 bar
并没有通过 Object.defineProperty
设置拦截,因此不是响应式属性。
这正是 Vue2 无法检测到新属性的原因。
三、解决方案
Vue2 不允许在已创建的实例上直接新增响应式属性,但我们有几种方式来解决:
1. 使用 Vue.set()
kotlin
Vue.set(this.item, "newProperty", "新属性");
这样 Vue 会调用内部的 defineReactive
,为新属性建立响应式绑定,并触发视图更新。
简化源码逻辑如下:
kotlin
function set(target, key, val) {
defineReactive(target, key, val);
dep.notify(); // 通知视图更新
return val;
}
2. 使用 Object.assign()
直接使用 Object.assign()
并不能触发更新,但如果创建一个新对象赋值给原对象,就可以:
kotlin
this.item = Object.assign({}, this.item, { newProperty: "新属性" });
这种方式适合一次性添加多个属性。
3. 使用 $forceUpdate()
强制让 Vue 实例重新渲染:
ini
this.item.newProperty = "新属性";
this.$forceUpdate();
不过,这种方式不推荐,因为大多数情况下说明代码结构存在问题。
四、小结
- 少量新增属性 → 使用
Vue.set()
。 - 大量新增属性 → 使用
Object.assign()
创建新对象。 - 临时解决方案 → 使用
$forceUpdate()
强制刷新(不建议)。
需要注意的是:Vue3 使用 Proxy 实现响应式,可以直接动态添加属性,依然能触发更新,不再存在这个问题。