Appearance
数组中属性的响应式
在JavaScript中数组是一个特殊的对象。
ts
const array = reactive(['a','b','c','d'])
effect(() => {
console.log(array[0])
})
setTimeout(() => {
array[0] = 'e'
}, 1000)数组也是能正确监听响应结果的,因为索引也是数组的属性。但是数组还有其他属性,比如length。
ts
const array = reactive(['a','b','c','d'])
effect(() => {
console.log(array.length)
})
setTimeout(() => {
array.length = 2
}, 1000)但是改变了length之后,数组就变成了['a','b'],这就意味着删除了c和d,那么如果之前访问了c,在修改length,会通知吗
ts
const array = reactive(['a','b','c','d'])
effect(() => {
console.log(array[2]) // c
})
setTimeout(() => {
array.length = 2 // 一秒钟没触发 effect
}, 1000)没有触发effect,就意味着需要手动触发依赖的effect重新执行了。现在修改length变成2,就是说之前收了所有>=length的依赖,都要触发更新。
ts
export trigger(target, key) {
// 先看一下之前有没收集过这个对象的依赖
let depsMap = targetMap.get(target)
if (!depsMap) {
// 没有收集过,直接返回
return
}
const targetIsArray = Array.isArray(target)
const newLength = target.length
// 此处处理修改 lenght 导致的副作用
if (targetIsArray && key === 'length') {
/**
* 一开始:['a', 'b', 'c', 'd'] length = 4
* 更新后:['a', 'b'] length = 2
*/
depsMap.forEach((dep, depKey) => {
if (depKey === 'length' || depKey >= newLength) {
// 通知更新
propagate(dep.subs)
}
})
} else {
// 看一下之前有没有收集过这个 key
let dep = depsMap.get(key)
if (!dep) {
// 如果这个 key 没收集过,直接返回
return
}
// 通知更新
propagate(dep.subs)
}
}当修改length之后,手动触发所有>=length的依赖更新,就可以了。
隐式更新length
什么叫隐式更新呢?比如push、pop这些方法,都会隐式更新length,他们在执行期间,会更新length。一旦有一个操作,触发了 length 的更新,那必然在 set 之前和 set 之后,它的 length 是不一样的,那么我们就可以利用这一点来选择对数组的 length 进行一个手动更新:
ts
// reactivity/src/baseHandler.ts
export const mutableHandlers = {
set(target, key, newValue, receiver) {
// 看一下 target 是不是一个数组
const targetIsArray = Array.isArray(target)
// set 前,拿到老的 length
const oldLength = targetIsArray ? target.length : 0
// 省略了一些不相关的代码
const res = Reflect.set(target, key, newValue, receiver)
// set 后拿到最新的 length
const newLength = targetIsArray ? target.length : 0
if (targetIsArray && newLength !== oldLength && key !== 'length') {
/**
* 如果更新之前和更新之后,length 不一样,代表隐式更新了,手动触发
*/
trigger(target, 'length')
}
return res
}
}