认识
2023年06月27日
一、认识
Vue.js 3.0
中, 可以通过 effect
中的 scheduler
配置项来自主控制副作用函数的执行顺序、执行次数。
二、细节
2.1 调用
scheduler
用法如下:
effect(
()=> { console.log(obj.a) },
{
scheduler(fn){
fn();
}
}
);
2.2 传递
scheduler
机制如下:
function effect(fn,options = {}){
const effectFn = ()=>{
activeEffect = effectFn;
cleanup(effectFn);
const res = fn();
return res;
}
effectFn.options = options;
effectFn.deps = [];
}
2.3 处理
scheduler
在 trigger
中的表现:
function trigger(target,key){
const depsMap = targetMap.get(target);
if(depsMap){
return
}
const effects = depsMap.get(key);
const effectsToRun = new Set(); // 通过 Set 集合存储 effects 并去重, 遍历 Set 而不是直接遍历 effects ,避免 `forEach` 时的无限递归。
// trigger 触发执行的副作用函数与当前正在执行的副作用函数相同, 则不触发执行,避免无限递归调用和栈溢出。
effects && effects.forEach((effectFn)=>{
if(effectFn !== activeEffect){
effectsToRun.add(effectFn);
}
});
effectsToRun.forEach(effectFn => {
if(effectFn.options.scheduler){
effectFn.options.scheduler(effectFn);
}else{
effectFn();
}
});
}
三、批量更新
Vue
批量更新处理时机在微任务中, 优先通过 Promise.then
处理。
const state = reactive({
num: 1
})
const jobQueue = new Set()
const p = Promise.resolve()
let isFlushing = false
const flushJob = () => {
if (isFlushing) {
return
}
isFlushing = true
// 微任务
p.then(() => {
jobQueue.forEach((job) => job())
}).finally(() => {
// 结束后充值设置为false
isFlushing = false
})
}
effect(() => {
console.log('num', state.num)
}, {
scheduler (fn) {
// 每次数据发生变化都往队列中添加副作用函数
jobQueue.add(fn)
// 并尝试刷新job,但是一个微任务只会在事件循环中执行一次,所以哪怕num变化了100次,最后也只会执行一次副作用函数
flushJob()
}
})
let count = 100
while (count--) {
state.num++
}
Vue
中状态 的变化会导致 effect
中 scheduler
执行, scheduler
中将副作用函数添加到任务队列, 并去重, 利用 Promise
微任务的特性,当num
被更改100
次之后同步代码全部执行结束后,then
回调将会被执行,此时num
已经是101
,而jobQueue
中也只有一个fn
,所以最终只会打印一次101