vue3-runtime(五) scheduler、nextTick

190 阅读1分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第19天,点击查看活动详情

这次讲schedulernextTick。解决:一个函数执行,多次改响应式数据会触发多次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++ 触发 effecttrigger 执行,连续三次就三次执行,为了解决这个重复渲染的问题,因此有了 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;
}