携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第19天,点击查看活动详情
这次讲scheduler
和nextTick
。解决:一个函数执行,多次改响应式数据会触发多次effect执行,但实际上执行一次就行了。
scheduler
一个函数执行,多次改值会触发多次effect执行,但实际上执行一次就行了。
举个例子:
const Comp = {
setup() {
const count = ref(0);
const add = () => {
count.value++;
count.value++;
count.value++;
};
return {
count,
add,
};
},
render(ctx) {
console.log('render');
return [
h('div', null, `count: ${ctx.count.value}`),
h(
'button',
{
onClick: ctx.add,
},
'add'
),
];
},
};
render(h(Comp), document.body);
由于 count.value++
触发 effect
的trigger
执行,连续三次就三次执行,为了解决这个重复渲染的问题,因此有了 scheduler
的概念
scheduler
的核心原理就是,当组件依赖的响应式数据发生改变时,不是立即去执行更新,而是将待执行的更新任务放入到下一个 javascript
微任务中去。待本轮的同步代码完全执行完成后,进入到下一个微任务周期时,再一起处理这些待执行的更新任务。这时候,就能对这些更新任务做去重处理了。
原本
count.value++
数据一改,就去执行相应的effect
,然后再回来继续执行第二个count.value++
。现在 数据一改,把
effect
放任务队列,启用微任务去执行这个任务队列,由于同步优先,继续去执行改数据操作,检查队列是否已有这个effect
,有就不放了,这样同步的那边数据改完了,这边任务队列里才只有一个,然后轮到微任务执行任务队列,这样就完美
代码
// 代理数据改动时触发的effect
instance.update = effect(
()=>{
if (!instance.isMounted) {....}
},
{//scheduler:用queueJob来代替trigger
scheduler: queueJob,
}
)
# scheduler.js
const queue = []; // 存放任务
let isFlushing = false; // 是否执行中
const resolvedPromise = Promise.resolve();
let currentFlushPromise = null; // 是否有正在执行的currentFlushPromise
export function queueJob(job) {
if (!queue.length || !queue.includes(job)) {
// 队列里空或者不重复的时候再加
queue.push(job);
// 微任务去执行队列
queueFlush();
}
}
function queueFlush() {
// 没有正在执行的任务队列
if (!isFlushing) {
// 执行开启
isFlushing = true;
// 放微任务执行队列
currentFlushPromise = resolvedPromise.then(flushJobs);
}
}
function flushJobs() {
// 执行任务
try {
for (let i = 0; i < queue.length; i++) {
const job = queue[i];
job();
}
// 记得清空初始化
} finally {
isFlushing = false;
queue.length = 0;
currentFlushPromise = null;
}
}
nextTick
但是这样也会导致一个问题,就是你执行完事件不能立刻取到返回的数值,需要等微任务队列执行完才能拿到,所以有nextTick
的概念。就是当微任务执行完后立刻执行的方法
代码
# scheduler.js
export function nextTick(fn) {
// 兼容 正在更新 或者 没有更新时
const p = currentFlushPromise || resolvedPromise;
// return p.then(fn) 这样子就能 保证fn在执行完微任务的异步队列 后就立刻执行
// 这样写 是为了兼容 await this.$nextTick(); 的语法 以同步的方法去等待异步队列执行完,这样下面获取就是最新的了。
return fn ? p.then(fn) : p;
}