Flexi-next/Packages/Runtime-core/SRC /scheduler.ts

In fact, the scheduling mechanism in this paper, namely scheduler, is to solve the scheduling problem of a rendering task during component rendering, or the problem of call timing, which is typical of “no, but better with it”. However, considering that nextTick is a very useful API and is very simple to implement, So let’s do that by the way

Why dispatch

A problem with instance.update, the component update method implemented earlier, is that the component is rerendered for every trigger in setup. In other words, the component is rerendered for every change in instance. CTX. The following example

const Comp = {
    setup() {
        const a = ref(0);
        const add = () = > {
            a.value++;
            a.value++;
        };
        return { a, add };
    },
    render(ctx) {
        // Print it here
        console.log('update...... ');

        return h('div'.null, [
            h('div'.null, ctx.a.value),
            h('button', { onClick: ctx.add }, 'add'),]); }}; render(h(Comp),document.body);
Copy the code

The page looks like this, starting with 0 and then printing “update….” when mounted. So far everything is as expected

And if I click on the Add button, it’s going to add 2, but look at the console, “Update….” Output twice

The same goes for clicking again

This example shows that when a component is updated, the component is rerendered each time the trigger is triggered, and the setup function usually does a lot of data updating, resulting in a lot of repeated component updates. What we want most of all is to update components after setup is fully executed, hence the concept of “task scheduling”, which simply means storing tasks and executing them when they should be executed.

Look at the

Scheduler is a scheduler that saves the update function to be executed and then executes it later. In addition, in the implementation of component rendering, scheduler is a scheduler that saves the update function and then executes it later. We use an effect method to monitor updates and mounts, which makes it clear that the Scheduler scheduling mechanism is largely based on this

But then again, even if you know you can rely on this implementation is not useful, because the core idea has not come out, the following analysis how to do

Once again, our goal is to update components after setup is complete, so if we want to save the update function, the best way to do that is to use a task queue, because first in, first out, we don’t have any problems with the order, and once in, we have some space to work with. Since you can control the timing of update execution by simply controlling when to queue out, the question then arises as to when to queue out.

Think of here actually honestly I have no idea can not come out, so I looked at the source code, then directly from the analysis into the explanation

Vue3’s scheduling mechanism is based on EventLoop, which puts the update function in the micro-task, so that the js mechanism itself can be used to complete this requirement. EventLoop is a very, very important concept that will not be described too much here

Write about the

It’s easy to write, and it’s easy to summarize what we need to do

  • Update missions to the team
  • Update tasks are executed in microtasks
  • Back to the way you were before you joined the team

The following is a step-by-step approach

The team

There’s nothing to say about joining the team, it’s very simple, but by the way, a simple filter can be made here to remove repetitive update tasks, as follows

const queue = [];

const queueJob = job= > {
    // Join the team
    if(! queue.length || ! queue.includes(job)) { queue.push(job);/ / the next stepqueueFlush(); }}Copy the code

perform

QueueFlush () is queueFlush() in the previous step. We need to check whether a microtask is currently executing, so we need an isFlushPending variable to mark it. I’m going to use a currentFlushPromise to record the microtasks that are currently being executed

let isFlushPending = false;
let currentFlushPromise = null;
const resolvePromise = Promise.resolve();

const queueFlush = () = > {
    if(! isFlushPending) { isFlushPending =true; currentFlushPromise = resolvePromise.then(flushJobs); }}const flushJobs = () = > {
    // Job may contain user code
    // use a try-catch package
    try {
        for (const job ofqueue) { job(); }}finally {
        / /...}}Copy the code

reduction

In flushJobs, a finally pit will be used to flush all variables into the next flushJobs update task

/ / just before
finally {
    isFlushPending = false;
    queue.length = 0;
    currentFlushPromise = null;
}
Copy the code

Try it on

That’s all it takes to add a scheduler to the component’s update function

instance.update = effect(() = > {
        if(! instance.isMounted) {// mount...
        } else {
           // update...
        }
    }, queueJob);
Copy the code

There is also a nextTick

NextTick can be implemented just by giving him the currentFlushPromise, as follows

const nextTick = fn= > {
    const p = currentFlushPromise || resolvePromise;
    return fn ? p.then(fn) : p;
}
Copy the code

The above determination of whether FN exists or not is to be compatible with async-await writing method. To be honest, I have not used this writing method before, but I just added this line incidentally

To run the

Again, the example above

const Comp = {
    setup() {
        const a = ref(0);
        const add = () = > {
            a.value++;
            a.value++;
        };
        return { a, add };
    },
    render(ctx) {
        // Print it here
        console.log('update...... ');

        return h('div'.null, [
            h('div'.null, ctx.a.value),
            h('button', { onClick: ctx.add }, 'add'),]); }}; render(h(Comp),document.body);
Copy the code

The same in the beginning

But from now on, I clicked on it and just typed out update….

The same goes for trying again

conclusion

EventLoop is really a very important concept in JS, and the implementation of this paper’s Scheduler can be said to be completely based on EventLoop. Simply speaking, the update task is executed in the micro task. Due to the existence of EventLoop, The update task will be executed after all the synchronization tasks have completed, which is our goal

Here is the complete code

const queue = [];
let isFlushPending = false;
let currentFlushPromise = null;
const resolvePromise = Promise.resolve();

const queueJob = job= > {
    if(! queue.length || ! queue.includes(job)) { queue.push(job); queueFlush(); }}const queueFlush = () = > {
    if(! isFlushPending) { isFlushPending =true; currentFlushPromise = resolvePromise.then(flushJobs); }}const flushJobs = () = > {
    try {
        for (const job ofqueue) { job(); }}finally {
        isFlushPending = false;
        queue.length = 0;
        currentFlushPromise = null; }}const nextTick = fn= > {
    const p = currentFlushPromise || resolvePromise;
    return fn ? p.then(fn) : p;
}
Copy the code