1.手动调用effect
如果我们在数据更新之后第一时间不想去更新视图,我们在官方的vue中就可以通过effect中的一个配置来实现,即effect.scheduler。因此我们也需要实现这个功能。
1. 首先我们在effect函数调用时传入第二个参数作为一个配置对象,并将这个配置对象合并到_effect中。
export function effect(fn, options?) {
1.// 创建一个响应式effect,数据变化后可以重新执行
const _effect = new ReactiveEffect(fn,() => {
_effect.run()
})
_effect.run() // 默认执行一次
// 为effect挂载options
if(options) {
Object.assign(_effect,options) // 覆盖掉之前的
}
}
由于我们前天在依赖调用是调用的是effect的scheduler,因此这个操作就会覆盖掉我们设置的scheduler。从而执行用户自定义的逻辑。
2. 接下来我们需要将effect的run方法作为值暴露出去为用时使用,当用户根据自身情况调用,同时在run函数上挂载当前的effect实例。
# effect.ts
function effect(fn, options?) {
...
// 将effect的run方法暴露给外部
const runner = _effect.run.bind(_effect)
runner.effect = _effect
return runner
}
2.嵌套调用
我们先来看这样一段代码
const user = reactive({
name:'张三',
age:18,
state:{
title: 1
}
})
effect(() => {
document.body.innerHTML = user.name
user.name = Math.random()
})
如上所示的代码,当effect执行之后在函数内部即读取了属性,又设置了属性值,此时就会连续触发effect的收集与执行,因此我们需要在依赖执行前判断当前依赖是否是正在执行,如果正在执行,则无需再触发。
我们先为ReactiveEffect设置一个属性咱们用于保存当前正在运行的effect数量, _running
# effect.ts
// 每次依赖执行前_running + 1,执行完后 - 1
try {
currentEffect = this
// 这里为了避免无用的effect依赖,每次触发收集之前将原先的依赖表清空
preClearEffect(this)
this._running++ // 记录当前正在运行的effect数量
return this.fn()
} finally {
// 删除以前依赖表中多余的依赖
postDepEffect(this)
this._running-- // 记录当前正在运行的effect数量
currentEffect = lastEffect
}
// 在触发依赖的位置判断是否需要触发
// 触发依赖
export function triggerEffect(deps) {
for(const effect of deps.keys()) {
if(effect.scheduler) {
// 判断当前是否已经有正在执行的effect
if(effect._running === 0) {
effect.scheduler() // 默认相当于调用了effect.run()
}
}
}
}
3.深度代理
当前reactive的实现中,如果数据是对象类型,则只对第一层进行代理,如果对象内部还有对象,则不会进行代理。
但是如果是对象嵌套对象形式,根据源码的逻辑,需要在读取到该对象的子对象是才对其子对象进行代理。因此我们需要在读取属性时判读所读取的属性是否是对象,如果是则进行代理。
# baseHandler.ts
export const mutableHandlers: ProxyHandler<any> = {
get(target,key,recevier) {
// 判断当前是否已经是响应式对象
if(key === ReactiveFlags.IS_REACTIVE) {
return true
}
// 取值时,需要将响应式属性与effect进行绑定 - 依赖收集
track(target,key) // 收集依赖
const result = Reflect.get(target,key,recevier)
// 深度代理
if(isObject(result)) {
return reactive(result)
}
return result
},
set: ...
}