本章内容是基于vue变量侦测的相关API,对vue变量侦测的源码不了解的同学可以先看这篇文章:
基本概念介绍:
上一章讲过vue的变量侦测存在缺陷,以下两种情况vue无法侦测到数据的变化,导致依赖无法被触发
1. object新增或者删除key
2. 通过下标修改array的值
针对这个缺陷,vue提供了$set, $del用于修改数据并触发依赖
$set源码:
/**
* Set a property on an object. Adds the new property and
* triggers change notification if the property doesn't
* already exist.
*/
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)}`)
}
// 如果target是数组,则通过splice方法修改数组
//由于vue的数组重写了splice方法,会在splice方法中触发依赖
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val
}
// 如果key已经在target中,直接赋值,无需手动触发依赖
// 因为target如果是响应式数据的话,target[key]被赋值时自然会触发依赖
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
// __ob__属性是响应式数据的标识
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
}
// 如果target不是响应式数据,直接赋值,无需触发依赖
if (!ob) {
target[key] = val
return val
}
// 如果target是响应式数据,监听新值的getter,setter,转为响应式数据
defineReactive(ob.value, key, val)
// 新增key,该动作没有被监听到需要手动触发
ob.dep.notify()
return val
}
$del源码:
/**
* Delete a property and trigger change if necessary.
*/
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)}`)
}
// 如果target是数组,则通过splice方法修改数组
//由于vue的数组重写了splice方法,会在splice方法中触发依赖
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.splice(key, 1)
return
}
// __ob__属性是响应式数据的标识
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
}
// target中没有这个key,直接return
if (!hasOwn(target, key)) {
return
}
// target中有这个key,删除Key
delete target[key]
// target不是响应式数据,无需触发依赖
if (!ob) {
return
}
// target是响应式数据,触发依赖
ob.dep.notify()
}
总结:
1. 针对array的侦测缺陷,直接通过调用splice方法修改数组。上一章我们讲过,array是通过重写数组原型方法触发依赖,所以通过直接调用splice方法来修改数组,可以触发依赖。
2. 针对object的侦测缺陷,直接调用 target.__ob__.dep.notify(), 手动去触发响应式数据的依赖。运用这个原理,我们也可以在代码中手动触发依赖,如:
delete this.obj.name;
this.obj.__ob__.dep.notify(); // 手动触发依赖
虽然可以实现同样效果,但还是建议使用$set、$del。