vue3源码学习-6-调度器

前言

经过第 5 章对于分支切换的操作之后,vue 的 effect 源码就具有了收集需要的依赖,对于改变不必要的数据,不会触发依赖的更新。那么今天就要实现 vue3 的调度器代码,之前 effect 只能同步运行代码,无法对于异步操作进行数据更新。
官方的写法是什么样子呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 函数如下编写了。

1
2
3
4
5
6
7
8
9
10
11
12
13
class ReactiveEffect{
......
constructor(public fn){
......
}
run(){
......
}
stop(){
this.active =false;
clearupEffect(this)
}
}

同样的 effect 函数也要做相应的修改,抛出当前的 runner 对象,然后通过这个对象上的 stop 来操作停止命令。

1
2
3
4
5
6
7
8
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 函数,增加一个对象,通过对象来传递用户编写的函数

1
2
3
4
5
6
7
8
9
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 参数。

1
2
3
4
5
6
class ReactiveEffect{
......
constructor(public fn, public schedule){
......
}
}

下一步就是修改 trigger 函数,这样变量的属性改变的时候就知道是不是执行 schedule 函数了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 函数了,如开篇中的例子

1
git:[@github/MicroMatrixOrg/vue3-plan/tree/effect_schedule]