跳到主要内容

认识

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 处理

schedulertrigger 中的表现:

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状态 的变化会导致 effectscheduler 执行, scheduler 中将副作用函数添加到任务队列, 并去重, 利用 Promise 微任务的特性,当num被更改100次之后同步代码全部执行结束后,then回调将会被执行,此时num已经是101,而jobQueue中也只有一个fn,所以最终只会打印一次101