前言
经过第5章对于分支切换的操作之后,vue的effect源码就具有了收集需要的依赖,对于改变不必要的数据,不会触发依赖的更新。那么今天就要实现vue3的调度器代码,之前effect只能同步运行代码,无法对于异步操作进行数据更新。
官方的写法是什么样子呢?
let flag = false;
let runner = effect(() => {
document.getElementById("app").innerHTML = "年龄:"+state.age
},{
schedule(){
if(!flag){
flag = true;
setTimeout(() => {
runner();
flag = false;
},1000)
}
}}
)
// runner.effect.stop();
state.age = 1000;
state.age = 2000;
state.age = 3000;
state.age = 4000;
state.age = 5000;
上面的代码最后只会渲染stage.age=5000这个样子。并且还具有了可以手动停止依赖的触发的操作stop()
编写stop()函数
首先,先完成能手动停止依赖的和触发。那么我就要有一个stop函数来完成关闭操作,之前通过run里面的函数渲染,使得属性触发了get操作。通过get函数里面编写的track函数,完成了依赖收集。然后通过trigger里面的依赖记录重新触发函数。在第5篇文章中,完成了依赖每次先清空再收集的函数,那么stop不就可以直接通过调用清空依赖,来达到停止触发的操作。同时把active设置为false,来关闭函数的更新。所以stop函数如下编写了。
class ReactiveEffect{
......
constructor(public fn){
......
}
run(){
......
}
stop(){
this.active =false;
clearupEffect(this)
}
}
同样的effect函数也要做相应的修改,抛出当前的runner对象,然后通过这个对象上的stop来操作停止命令。
export function effect(fn, options: any = {}) {
// 这里的fn可以根据状态的变化,重新执行,effect可以嵌套着写
const _effect = new ReactiveEffect(fn) //创建响应式的effect
_effect.run() //默认先执行一次
const runner = _effect.run.bind(_effect)// 绑定this执行
runner.effect = _effect // 将_effect挂载到runner上
return runner
这样就可以在拿到effect到返回函数之后,通过runner.effect.stop()
来手动停止执行。
schedule调度函数实现
当用户在effect中的schedule中写了一个自己的一个操作,那么就能通过用户是否写了schedule函数来判断是否要执行用户的schedule,还是执行run()。
了解了大致思路,我们先修改effect函数,增加一个对象,通过对象来传递用户编写的函数
export function effect(fn, options: any = {}) {
// 这里的fn可以根据状态的变化,重新执行,effect可以嵌套着写
const _effect = new ReactiveEffect(fn, options.schedule) //创建响应式的effect
_effect.run() //默认先执行一次
const runner = _effect.run.bind(_effect) // 绑定this执行
runner.effect = _effect // 将_effect挂载到runner上
return runner
}
同时ReactiveEffect类上在构造函数上加入schedule参数。
class ReactiveEffect{
......
constructor(public fn, public schedule){
......
}
}
下一步就是修改trigger函数,这样变量的属性改变的时候就知道是不是执行schedule函数了
export function trigger(target, type, key, value, oldValue) {
const depsMap = targetMap.get(target)
if (!depsMap) return //触发的值不在模版中
let effects = depsMap.get(key)
// 此处做逻辑修改,因为set在删除之后,再做添加,那么会造成死循环,有些方法会对数据拷贝之后再做修改
// 可以避免这个问题
if (effects) {
effects = new Set(effects)
effects.forEach((effect) => {
if (activeEffect !== effect) {
if (effect.schedule) {
effect.schedule() // 用户传入schedule的时候,就调用回调
} else {
effect.run() // 否则就刷新
}
}
// 如果这里直接就写effect.run(),那么会遇到这种情况,在模版中赋值,那么也会触发这个,
// 然后又通过了依赖收集的时候,运行它的第一次run()。就会导致循环调用,爆栈,
//所以这里需要加一个判断是否是当前的effect,如果是的话,就忽略这一次的赋值触发的run();
//注意目前的代码是不支持异步的
})
}
}
到此为止,就能在测试html上调用自己写的schedule函数了,如开篇中的例子
评论区