认识vue3的调度器scheduler
调度器
是vue3响应式系统中一个非常重要的特性,可调度性指的是当trigger
动作触发副作用函数重新执行时,有能力决定副作用函数执行的时机、次数以及方式
const data = { foo: 1 }
const obj = new Proxy(data, { /* ... */ }) // 上文中的响应式
effect(() => {
console.log(obj.foo)
})
obj.foo++
console.log('结束了')
正常执行结果顺序是1,2,结束了
,但是,若我们期望的打印顺序发生改变1,结束了,2
,要实现这样的打印结果,就需要使用到调度器
- 可以为函数effect函数设计一个选项参数
options
,允许用户指定调度器:
effect(
() => {
console.log(obj.foo);
},
// options
{
// 调度器 scheduler 是一个函数
scheduler(fn) {
// ...
},
});
- 将调度器对象挂在到当前副作用函数中
// effect 函数用于注册副作用函数
function effect(fn, options = {}) {
fn.options = options; // 新增挂在调度器
activeEffect = fn;
// 执行副作用函数
fn();
}
- 修改参数时,判断是否存在调度器,存在,执行当前挂载中调度器方法
(在taigger函数中)
function trigger(target, key) {
// 根据target从桶中取得depsMap,它是key --> effects
const depsMap = bucket.get(target);
if (!depsMap) return;
// 根据key取得当前对应的副作用函数
const effects = depsMap.get(key);
// 执行副作用函数
effects && effects.forEach((fn) => {
// fn()
if (fn.options.scheduler) { // 新增
fn.options.scheduler(fn);
} else {
// 否则直接执行副作用函数(之前的默认行为)
fn();
}
});
}
- 使用
setTimeout
开启一个宏任务来执行副作用函数 fn,这样,就能改变执行顺序,拿到我们想要的执行结果
function effect(fn, options = {}) {
fn.options = options; // 新增
activeEffect = fn;
// 执行副作用函数
fn();
}
effect(
() => {
console.log(obj.foo);
},
// options
{
// 调度器 scheduler 是一个函数
scheduler(fn) {
// 修改参数,将副作用函数放在宏任务队列中执行
setTimeout(fn)
},
}
);
完整代码:
const data = { foo: 1 };
// 用一个全局变量存储被注册的副作用函数
let activeEffect;
// 创建一个新桶来存储副作用函数,包含key和value
const bucket = new WeakMap();
const obj = new Proxy(data, {
get(target, key) {
// target:当前对象,key:触发监听的key
track(target, key);
return target[key];
},
set(target, key, newVal) {
target[key] = newVal;
trigger(target, key);
},
});
// track函数
function track(target, key) {
// 没有正在执行的副作用函数 直接返回
if (!activeEffect) return target[key];
// 从这个桶中取出一个Map类型(key -> value)
let depsMap = bucket.get(target);
// 不存在,则创建一个Map与target关联
if (!depsMap) {
bucket.set(target, (depsMap = new Map()));
}
// 根据key判断每个key上是否存在对应的副作用函数
let deps = depsMap.get(key);
// 不存在,则新建一个new Set,并与key关联
if (!deps) {
depsMap.set(key, (deps = new Set()));
}
// 最后将当前激活的副作用函数添加到桶中
deps.add(activeEffect);
}
// trigger函数
function trigger(target, key) {
// 根据target从桶中取得depsMap,它是key --> effects
const depsMap = bucket.get(target);
if (!depsMap) return;
// 根据key取得当前对应的副作用函数
const effects = depsMap.get(key);
// 执行副作用函数
effects &&
effects.forEach((fn) => {
if (fn.options.scheduler) {
fn.options.scheduler(fn);
} else {
// 否则直接执行副作用函数(之前的默认行为)
fn();
}
});
}
// effect 函数用于注册副作用函数
function effect(fn, options = {}) {
fn.options = options; // 新增
activeEffect = fn;
// 执行副作用函数
fn();
}
effect(
() => {
console.log(obj.foo);
},
// options
{
// 调度器 scheduler 是一个函数
scheduler(fn) {
// 修改参数,将副作用函数放在宏任务队列中执行
setTimeout(fn);
},
}
);
obj.foo++;
console.log("结束了");
除了控制副作用函数的执行顺序,通过调度器
还可以做到控制它的执行次数
- 正常的打印结果应该是
1,2,3
,但是我们只关心执行的最后结果,应该拿到的是1,3
,执行三次有些多余,这时,就需要使用到调度器
来修改执行次数
const data = { foo: 1 }
const obj = new Proxy(data, { /* ... */ }) // 上文中的响应式
effect(() => {
console.log(obj.foo)
})
obj.foo++
obj.foo++;
- 实现不包含过渡阶段,使用调度器基于
promise
,可以直接修改当前当前调度函数
// 定义一个任务队列,使用它自动去重能力
const jobQueue = new Set();
// 使用 Promise.resolve() 创建一个 promise 实例,我们用它将一个任务添加到微任务队列
const p = Promise.resolve();
// 一个标志代表是否正在刷新队列
let isFlushing = false;
function flushJob() {
// 如果队列正在刷新,则什么都不做
if (isFlushing) return;
// 设置为 true,代表正在刷新
isFlushing = true;
// 在微任务队列中刷新 jobQueue 队列
p.then(() => {
jobQueue.forEach((job) => job());
}).finally(() => {
// 结束后重置 isFlushing
isFlushing = false;
});
}
- 执行调度函数
effect(
() => {
console.log(obj.foo);
},
// options
{
// 调度器 scheduler 是一个函数
scheduler(fn) {
jobQueue.add(fn);
// 调用 flushJob 刷新队列
flushJob();
},
}
);
obj.foo++;
obj.foo++;
在这里只是简单的认识下调度器
是什么,有什么功能,怎么使用,而在vue3中的有一个更加完善的的调度器功能,但是思路与上文差不多。
转载自:https://juejin.cn/post/7240623529519743032