Vue2 对象新增属性不响应

解决 Vue2 中动态添加对象属性时视图不更新的问题

问题

在 Vue2 中给对象动态添加新属性时,数据虽然更新了,但页面不会重新渲染。

<template>
  <div>
    <p v-for="(value, key) in item" :key="key">
      {{ value }}
    </p>
    <button @click="addProperty">添加属性</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      item: {
        oldProperty: "旧属性"
      }
    }
  },
  methods: {
    addProperty() {
      this.item.newProperty = "新属性"  // 数据更新了,但页面不刷新
      console.log(this.item)  // 能看到 newProperty
    }
  }
}
</script>

解答

原因分析

Vue2 使用 Object.defineProperty 实现响应式,只能拦截已存在的属性:

const obj = {}
Object.defineProperty(obj, 'foo', {
  get() {
    console.log(`get foo:${val}`)
    return val
  },
  set(newVal) {
    if (newVal !== val) {
      console.log(`set foo:${newVal}`)
      val = newVal
    }
  }
})

obj.foo = 'new'  // 触发 setter
obj.bar = '新属性'  // 不会触发任何拦截

初始化时 item.oldProperty 被设置成响应式,但后续添加的 newProperty 没有经过 Object.defineProperty 处理,因此不是响应式的。

解决方案

方案一:使用 Vue.set()

methods: {
  addProperty() {
    this.$set(this.item, 'newProperty', '新属性')
    // 或者
    Vue.set(this.item, 'newProperty', '新属性')
  }
}

方案二:使用 Object.assign()

创建新对象替换原对象:

methods: {
  addProperty() {
    this.item = Object.assign({}, this.item, {
      newProperty1: '新属性1',
      newProperty2: '新属性2'
    })
  }
}

方案三:使用 $forceUpdate()

强制重新渲染(不推荐):

methods: {
  addProperty() {
    this.item.newProperty = '新属性'
    this.$forceUpdate()
  }
}

关键点

  • Vue2 的 Object.defineProperty 无法检测对象新增属性,只能拦截已存在的属性
  • 添加少量属性用 Vue.set()this.$set()
  • 添加多个属性用 Object.assign() 创建新对象
  • $forceUpdate() 可以强制刷新,但应该作为最后手段,通常说明代码有问题
  • Vue3 使用 Proxy 实现响应式,可以直接检测新增属性,不存在这个问题