# $set$delete 的原理

dataprops 的处理》部分,解释了“Vue 无法检测到对象属性的添加或删除”的原因:

  • 添加和删除属性的操作无法被 Object.defineProperty() 劫持
  • 新添加的属性不会被 Vue 处理为响应式数据
  • Vue 只在初始化时,将数据转换为响应式数据

# $set()

但是如果需要添加响应式属性,也是可以办到的 - Vue.prototype.$set()Vue.set(),这两个方法是一模一样的。

看例子:

const vm = new Vue({
  data() {
    return {
      name: {
        firstname: 'nail'
      }
    }
  }
})

vm.$set(vm.name, 'lastname', 'hunter')

先定义该函数,既然是添加响应式属性,那就一定会用到之前的 defineReactive() 函数

Vue.prototype.$set = function(target, key, value) {
  defineReactive(target, key, value)
}

# 添加后通知依赖更新

上面的代码给对象上添加了一个响应式数据,但是并不会通知依赖进行更新,假设如下场景时:

const vm = new Vue({
  data() {
    return {
      name: {
        firstname: 'nail'
      }
    }
  },
  template: `<div>
    <p v-if="name.firstname && name.lastname">
      hello {{name.firstname}}.{{name.lastname}}
    </p>
  </div>`
})

vm.$set(vm.name, 'lastname', 'hunter')

在添加 lastname 属性后,希望组件会重新渲染从而更新页面。所以就需要对依赖进行通知,那么怎么才能获取到依赖列表?

恰好在dataprops 的处理》中为了解决数组的依赖通知问题,在 Observer 类上添加了依赖列表,现在我们就可以再次利用它:

Vue.prototype.$set = function(target, key, value) {
  // 新增代码
  const ob = target.__ob__

  defineReactive(target, key, value)
  // 新增代码
  ob.dep.notify()
}

# 处理 Array 类型的对象

上面的代码并不适用于数组类型的响应式对象,好在现在 Array 的某些副作用方法是可以通知依赖进行更新的:

Vue.prototype.$set = function(target, key, value) {

  // 新增代码
  if (Array.isArray(target)) {
    target.length = Math.max(target.length, key)
    // 利用了数组的拦截器的方法
    target.splice(key, 1, value)
    return
  }

  // 省略多余代码
}

# $delete()

通过 $delete() 删除响应式属性的目的是通知视图更新,仍旧用这个例子:

const vm = new Vue({
  data() {
    return {
      name: {
        firstname: 'nail',
        lastname: 'hunter'
      }
    }
  },
  template: `<div>
    <p v-if="name.firstname && name.lastname">
      hello {{name.firstname}}.{{name.lastname}}
    </p>
  </div>`
})

vm.$delete(vm.name, 'lastname')

当删除属性 lastname 后,需要视图更新,所以 $delete 做的事情很简单:

  • 删除属性
  • 通知视图更新
Vue.prototype.$delete = function(target, key) {

  // 数组仍旧使用拦截器中的方法
  if (Array.isArray(target)) {
    target.splice(key, 1)
    return
  }

  const ob = target.__ob__

  delete target[key]

  ob.dep.notify()
}