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