vue源码set、delete源码解析

336 阅读2分钟

问题

export default {
    data() {
        return {
            obj: {
                a: 1,
                b: 2
            }
        }
    }
}
  1. 当我们动态向obj对象中添加属性时,this.obj.c = 3;
  2. 当我们动态删除obj对象中的一个属性时, delete this.obj.b

上述两种情况,数据是否还是响应式的?页面是否还会更新?通过调试我们发现,这样页面没有更新。在Vue初始化实例的时候,会遍历data中的属性,如果是对象的话,那就遍历对象中的每个属性,为每个数据添加响应式处理,所以现在动态的添加属性或删除属性,vue并没有做处理,所以不会触发派发更新的操作。

解决方案

vue提供了setdelete方法,来处理对象动态添加和删除属性的操作。

Vue.set官方解释

  • 参数

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

  • 用法

    向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新 property,因为 Vue 无法探测普通的新增 property (比如 this.myObject.newProperty = 'hi')

Vue.delete官方解释

  • 参数

    • {Object | Array} target

    • {string | number} propertyName/index

    仅在 2.2.0+ 版本中支持 Array + index 用法。

  • 用法

    删除对象的 property。如果对象是响应式的,确保删除能触发更新视图。这个方法主要用于避开 Vue 不能检测到 property 被删除的限制,但是你应该很少会使用它。

所以我们可以用setdelete方法来解决上述的问题

源码

  1. set源码
export function set (target: Array<any> | Object, key: any, val: any): any {
  if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
  }
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key)
    target.splice(key, 1, val)
    return val
  }
  if (key in target && !(key in Object.prototype)) {
    target[key] = val
    return val
  }
  const ob = (target: any).__ob__
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid adding reactive properties to a Vue instance or its root $data ' +
      'at runtime - declare it upfront in the data option.'
    )
    return val
  }
  if (!ob) {
    target[key] = val
    return val
  }
  defineReactive(ob.value, key, val)
  ob.dep.notify()
  return val
}

首先判断参数不能是undefined、null以及基本数据类型,如果是数组的话会调用修改后的splice方法,让新增的值变成响应式数据,如果是对象的话则调用defineReactive方法将属性变成响应式数据,最后调用notify方法,来通知watcher来更新数据。还可以看到如果直接修改根节点的$data或者修改一个vue实例的话,直接会返回,并给出警告,这也是官方文档上写的目标对象不能是一个 Vue 实例或 Vue 实例的根数据对象。

  1. delete源码
export function del (target: Array<any> | Object, key: any) {
  if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(`Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`)
  }
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.splice(key, 1)
    return
  }
  const ob = (target: any).__ob__
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid deleting properties on a Vue instance or its root $data ' +
      '- just set it to null.'
    )
    return
  }
  if (!hasOwn(target, key)) {
    return
  }
  delete target[key]
  if (!ob) {
    return
  }
  ob.dep.notify()
}

Vue.delete实现原理和set类似,只不过是删除属性,在删除完属性之后调用notify方法来通知watcher进行更新数据。