Vue源码分析之$set

115 阅读2分钟

这是我参与2022首次更文挑战的第10天,活动详情查看:2022首次更文挑战

前言

前边看Vue的$forceUpdate方法时提到过官方对于一些Vue无法检测到的对象属性的添加,最好的方法并不是调用$forceUpdate去强制刷新视图,而是使用Vue的实例方法$set去处理。

这篇文就来看下Vue源码中set的具体实现:

Vue.set

Vue.set是Vue中的全局API,可以在任何地方调用,官方是这样介绍Vue.set的:

image.png

Vue.set方法定义在src/core/global-api/index.js中:

export function initGlobalAPI (Vue: GlobalAPI) {
  ...
  Vue.set = set
  ...
}

global-api这里定义了很多全局api,代码都很简单,Vue.set就是直接引用了set方法。

vm.$set

Vue还有个实例方法$set,它的用法和Vue.set一样,官方说这是Vue.set的别名。

image.png

$set方法定义在src/core/instance.state.js中:

export function stateMixin (Vue: Class<Component>) {
  ...
  Vue.prototype.$set = set
  ...
}

从代码也看出来了,为什么说$set是Vue.set的别名,因为它们都指向了同一个set方法。

接下来看下这个set方法的实现:

set

set定义在src/core/observer/index.js中:

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
}

当初在看响应式的实现逻辑的时候有看到过这个方法,但并没有认真去看里边的实现过程,原来是暴露给全局用的方法。

  • 首先判断要设置的目标对象target是不是符合规范,如果是原始值则抛出警告。

  • 接下来判断目标对象是不是数组且key为一个有效的数组下标,则调用数组的splice方法将对应的属性值插入key指定的位置,同时把数组的长度置为key和原来长度之中的最大值,返回val值。

  • 若不是数组,那就是对象了。判断key是不是本身就在target对象中(这里有个细小的点是判断了这个key不能是存在于对象原型的那些原生属性),如果存在,则修改key属性的value值,返回val。

  • 接下来判断目标对象是不是根对象,我们在文档中也看到过被设置的目标对象不能是Vue实例,或者Vue实例的根数据对象。

  • 接下来判断目标对象本身是否是响应式的(在响应式对象那块已经知道如果一个属性有__ob__,说明它是一个响应式对象),如果target本身就不是响应式,则设置完key属性后,直接返回。

  • 接下来就是该方法的真正用处了,target本身是响应式对象,则调用defineReactive方法对key进行依赖收集,然后返回val。

isValidArrayIndex

这里有个判断数组下标是否合法的工具函数isValidArrayIndex,定义在src/shared/util.js中:

export function isValidArrayIndex (val: any): boolean {
  const n = parseFloat(String(val))
  return n >= 0 && Math.floor(n) === n && isFinite(val)
}

我想了下,要是我写的话,我可能会写Number(val) === parseInt(val) && val >=0 && isFinite(val)