这是我参与2022首次更文挑战的第10天,活动详情查看:2022首次更文挑战
前言
前边看Vue的$forceUpdate方法时提到过官方对于一些Vue无法检测到的对象属性的添加,最好的方法并不是调用$forceUpdate去强制刷新视图,而是使用Vue的实例方法$set去处理。
这篇文就来看下Vue源码中set的具体实现:
Vue.set
Vue.set是Vue中的全局API,可以在任何地方调用,官方是这样介绍Vue.set的:
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的别名。
$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)