动态给 vue 的 data 添加一个新的属性时会发生什么?怎样解决?

答:

会发生数据虽然更新了,但是页面没有更新。

为什么?

Vue 2 是用过 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
    }
  }
})

当我们访问 foo 属性或者设置 foo 值的时候都能够触发 setter 与 getter。但是我们为 obj 添加新属性的时候,却无法触发事件属性的拦截。

原因是一开始 obj 的 foo 属性被设成了响应式数据,而 bar 是后面新增的属性,并没有通过 Object.defineProperty 设置成响应式数据。

怎么解决?

Vue 不允许在已经创建的实例上动态添加新的响应式属性。若想实现数据与视图同步更新,可采取下面三种解决方案:

1. Vue.set()

Vue.set(target, propertyName/index, value)

  • 参数:{Object | Array} target、{string | number} propertyName/index、{any} value。
  • 返回值:设置的值。

通过 Vue.set 向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。

Vue.set() 无非再次调用 defineReactive 方法,实现新增属性的响应式。关于 defineReactive 方法,内部还是通过 Object.defineProperty 实现属性拦截。

this.$set(this.obj, 'newKey', 'newValue')

2. Object.assign()

直接使用 Object.assign() 添加到对象的新属性不会触发更新。应创建一个新的对象,合并原对象和混入对象的属性:

this.someObject = Object.assign({}, this.someObject, {
  newProperty1: 1,
  newProperty2: 2
})

3. $forceUpdate

如果你发现你自己需要在 Vue 中做一次强制更新,99.9% 的情况是某个地方做错了事。

$forceUpdate 迫使 Vue 实例重新渲染。

PS:仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。

补充:Vue 3 由于使用了 Proxy 实现响应式,对象新增 / 删除属性天然就是响应式的,不再需要上面这些特殊处理。

来源整理自:vue3js.cn 面试官系列