现在可以着手实现Set类型数据的响应式方案了。以下面的代码为例:
const p = reactive(new Set([1,2,3]))
effect(()=>{
// 在副作用函数内访问size属性
console.log(p.size)
})
//增加值为1的元素,应该触发响应
p.add(1)
p.add函数向集合中添加数据,这个行为会间接改变集合size的属性值,所有期望是副作用函数会重新执行。为了实现效果,需要在访问size时调用track函数进行依赖追踪,然后在add方法执行时调用trigger函数触发响应。
下面代码展示如何进行依赖追踪
function createReactive(obj, isShallow = false, isReadonly = false){
return new Proxy(obj, {
get(target, key, receiver){
if(key === 'size'){
// 调用track函数建立响应联系
track(target, ITERATE_KEY)
return Reflect.get(target, key, target)
}
return target[key].bind(target)
}
})
}
这里需要注意,响应联系需要建立在ITERATE_KEY于副作用函数之间,因为任何新增、删除操作都会影响size。
接下来看如何触发响应,显然我们需要实现一个自定义的add方法才行,如下代码:
// 定义一个对象,将自定义的add方法定义到该对象下
const mutableInstrumentations = {
add(key){/*...*/}
}
function createReactive(obj, isShallow = false, isReadonly = false){
return new Proxy(obj, {
get(target, key, receiver){
if(key === 'raw') return target
if(key === 'size'){
// 调用track函数建立响应联系
track(target, ITERATE_KEY)
return Reflect.get(target, key, target)
}
// 返回定义在mutableInstrumentations对象下的方法
return mutableInstrumentations[key]
}
})
}
在这里首先定义一个对象,将所有自定义实现的方法都定义到该对象下,然后,在get拦截函数内返回。这样通过p.add获取方法时,得到的是自定义的mutableInstrumentations.add方法。有了自定义实现的方法后,就可以在其中调用trigger函数触发响应了。
const mutableInstrumentations = {
add(key){
// this仍然指向的是代理对象,通过raw属性获取原始数据对象
const target = this.raw
// 通过原始数据对象执行add方法
// 注意这里不需要使用bind方法了,因为是直接通过target调用
const res= target.add(key)
// 调用trigger函数触发响应,指定操作类型为ADD
trigger(target, key, 'ADD')
// 返回操作结果
return res
}
}
而且当调用add方法添加的元素已经存在于Set集合中,就不再需要触发响应了,这样对性能更友好,因此可以对add方法进行优化
const mutableInstrumentations = {
add(key){
const target = this.raw
// 先判断值是否一斤存在
const hadKey = target.has(key)
// 只有在值不存在的情况下,才需要触发响应
if(!hadKey){
const res = target.add(key)
trigger(target, key, 'ADD')
}
return res
}
}
在实现add的思路上可以很轻松地实现delete方法
const mutableInstrumentations = {
delete(key){
const target = this.raw
const hadKey = target.has(key)
const res = target.delete(key)
// 当要删除的元素确实存在时,才触发响应
if(hadKey){
trigger(target, key, 'DELETE')
}
return res
}
}