替换整个对象时要考虑是否会破坏响应式

在 Vue2.x 中,如果你在组件的 data 里这样写:

data() {
  return {
    form: {}
  }
},
watch: {
  'form.a'(newVal, oldVal) {
    // 监听 form.a 的变化
    console.log('form.a changed:', oldVal, '→', newVal)
  }
}

然后在某个业务流程里执行了

this.form = {}

会带来以下几个影响:


1. 触发一次「从旧对象到新对象」的 watcher

当你把 this.form 整个替换成一个新的空对象时,Vue 会:

  1. 卸载 对原来那个 form 对象上所有依赖于 form.a 的响应式追踪;
  2. 重新执行 表达式 form.a,把它看作一个新的依赖。

因为旧的 form.a(可能是 undefinednull、或者你之前赋过的某个值)被替换成新对象上的 undefined,所以 watcher 会立刻以

oldVal = (原来 form.a),newVal = undefined

的形式触发一次。


2. 新对象上并没有 “a” 这个响应式属性

新赋值的 form = {} 本身是响应式的——Vue 会把这个新的对象走一遍 Observer—but 它目前没有任何名为 a 的属性:

console.log(this.form.a) // => undefined
  • 由于 JS 的限制,对象上新加的属性默认 不是响应式(也就不会被 Vue 的依赖收集追踪到)。
  • 也就是说,如果之后你写 this.form.a = 123是不会 再触发 watch: { 'form.a': … } 的。

3. 如何让新对象上的 a 继续被监听?

如果你的业务确实需要动态给 form.a 赋值,并且希望继续触发 watcher,有两种常见做法:

  1. 在 data 里提前声明

    data() {
      return {
        form: {
          a: null  // 提前声明好 a,保证从一开始就是响应式的
        }
      }
    }
    
  2. 使用 Vue.set / this.$set

    // 替换对象后,给新对象动态添加 a
    this.$set(this.form, 'a', 123)
    // 这样 Vue 就会为新加的属性走一遍响应式系统,watcher 会被触发
    

小结

  • 替换整个对象this.form = {})时,Vue 会先卸载旧的依赖,再重新“评估” form.a,因此会触发一次从旧值到 undefined 的 watcher。
  • 新对象没有预先声明的 a 属性,之后直接 this.form.a = … 不会被 Vue 监听到。
  • 若要继续监听新对象的 a,要么初始化时就写好,要么用 Vue.set(this.form, 'a', value) 来添加。

另外
在 Vue2.x 中,如果你在 data 里这样声明:

data() {
  return {
    form: { a: null }
  }
},
watch: {
  'form.a'(newVal, oldVal) {
    console.log('form.a changed:', oldVal, '→', newVal)
  }
}

然后在业务流程里直接执行:

this.form = {}

会带来两个主要影响:


1. 会触发一次从 nullundefined 的 watcher

  • 当你把整个 form 对象替换成新对象时,Vue 会把旧对象上的所有响应式依赖卸载掉,
    然后在新对象上重新建立依赖。

  • 因为旧对象上的 form.anull,新对象上没有 a 属性,取值时会得到 undefined
    所以 watcher 会立刻以

    form.a changed: null → undefined
    

    的形式被触发一次。


2. 新对象上的 a 属性不再是响应式的

  • 新的 {} 里没有预先声明 a,此时如果你再写

    this.form.a = 123
    

    这会在对象上动态添加一个普通属性,不会走 Vue 的响应式系统,
    因此 不会 触发你对 form.a 的 watcher。


如何在“重置”或“清空”form 时不破坏 form.a 的响应式?

方案 1:不要替换整个对象,只修改它的内容

  1. 使用 Object.assign 回填默认值

    // 定义一个默认模板
    const defaults = { a: null, b: '', c: [] }
    // 把 defaults 的所有属性复制到 this.form 上
    Object.assign(this.form, defaults)
    // 此时 this.form 仍是同一个对象,其上已声明的属性依旧响应式
    
  2. 手动删除 or 重置无用字段

    // 删除所有不需要的字段
    Object.keys(this.form).forEach(key => {
      if (key !== 'a') {
        this.$delete(this.form, key)
      }
    })
    // 明确重置 a
    this.form.a = null
    

这样 Vue 会一直监听同一个 form 对象,既不会触发 “null → undefined” 那次多余的 watcher,也能保持后续 this.form.a = xxx 的响应式。


方案 2:替换后用 Vue.set(或 $set)重新声明属性

如果你确实需要把 form 换成一个全新的对象,也可以这样做:

// 故意替换成空对象
this.form = {}

// 用 $set 重新添加 a,确保它是响应式的
this.$set(this.form, 'a', null)
// —— 此后 this.form.a = 123 会触发 watcher

注意:Vue2.x 里新增属性必须走 $set 才能变成响应式。


方案 3:深度监听整个 form

如果你的表单字段非常多、动态性强,也可以把 watcher 改为深度监听完整表单:

watch: {
  form: {
    handler(newForm, oldForm) {
      console.log('form 对象发生变化了:', newForm)
    },
    deep: true
  }
}
  • 优点:任何新增、删除、修改都会触发一次回调。
  • 缺点:回调里只拿到完整对象,不能像 form.a 那样精准区分是谁变了,需要自己做判断。

小结

  • 替换整个 form 会产生一次 null → undefined 的 watcher,并且之后新增的 a 不再响应式。
  • 推荐做法:不替换根对象,而是用 Object.assign、手动增删字段、或 Vue.set 等方式来 更新内容,保持同一个对象的响应式链路。
  • 必须新增属性时,记得走 Vue.set(this.form, 'key', value),否则 watcher 和绑定都不会生效。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容