React source code: scheduleWork

ScheduleCallbackForRoot () : immediately executes a scheduler after render()

Source:

// Use this function, along with runRootCallback, to ensure that only a single

// callback per root is scheduled. It's still possible to call renderRoot

// directly, but scheduling via this function helps avoid excessive callbacks.

// It works by storing the callback node and expiration time on the root. When a

// new callback comes in, it compares the expiration time to determine if it

// should cancel the previous one. It also relies on commitRoot scheduling a

// callback to render the next level, because that means we don't need a

// separate callback per expiration time.

// Call callback synchronously

// Access callback and expirationTime on root.

// When a new callback is called, compare to update expirationTime

function scheduleCallbackForRoot(

  root: FiberRoot,

  priorityLevel: ReactPriorityLevel,

  expirationTime: ExpirationTime,

{

  // Get the root callback expiration time

  const existingCallbackExpirationTime = root.callbackExpirationTime;

  // Update the root callback expiration time

  if (existingCallbackExpirationTime < expirationTime) {

    // New callback has higher priority than the existing one.

    // When a new expirationTime has a higher priority than an existing expirationTime

    const existingCallbackNode = root.callbackNode;

    if(existingCallbackNode ! = =null) {

      // Cancel existing callback (interrupt)

      // Remove the existing callback node from the list

      cancelCallback(existingCallbackNode);

    }

    / / update the callbackExpirationTime

    root.callbackExpirationTime = expirationTime;

    // If it is a synchronization task

    if (expirationTime === Sync) {

      // Sync React callbacks are scheduled on a special internal queue

      // Synchronize the scheduled callback in the temporary queue

      root.callbackNode = scheduleSyncCallback(

        runRootCallback.bind(

          null.

          root,

          renderRoot.bind(null, root, expirationTime),

        ),

      );

    } else {

      let options = null;

      if(expirationTime ! == Never) {

        //(Sync-2 - expirationTime) * 10-now()

        let timeout = expirationTimeToMs(expirationTime) - now();

        options = {timeout};

      }

      //callbackNode is a new process-wrapped task

      root.callbackNode = scheduleCallback(

        priorityLevel,

        //bind() means bind this, xx.bind(y)() to execute

        runRootCallback.bind(

          null.

          root,

          renderRoot.bind(null, root, expirationTime),

        ),

        options,

      );

      if (

        enableUserTimingAPI &&

expirationTime ! == Sync &&

        (executionContext & (RenderContext | CommitContext)) === NoContext

      ) {

        // Scheduled an async callback, and we're not already working. Add an

        // entry to the flamegraph that shows we're waiting for a callback

        // to fire.

        // Flag to start scheduling callback

        startRequestCallbackTimer();

      }

    }

  }



  // Associate the current interactions with this new root+priority.

  // Trace these updates and count them to see if they report errors

  schedulePendingInteractions(root, expirationTime);

}

Copy the code

Analytic: Fiber mechanism for each update task priority, and can record where scheduling (schedulePendingInteractions ())

ScheduleCallbackForRoot ()). The scheduleCallbackForRoot() scheduleCallbackForRoot() scheduleCallbackForRoot() scheduleCallbackForRoot() scheduleCallbackForRoot() You must wait for the setState UPDATE queue to complete before performing any further operations.

Take a look at what scheduleCallbackForRoot() does: (1) When the priority of the new scheduleCallback is higher, the current task cancelCallback(existingCallbackNode) is interrupted. (2) If the task is synchronous, it is scheduled in a temporary queue. (3) If the task is asynchronous, Then update the status of the scheduling queue. (4) Set the time node when scheduling starts. (5) Track the scheduled task

For specific explanation, please read down patiently

CancelCallback () cancelCallback() interrupts an ongoing scheduling task

Source:

const {

  unstable_cancelCallback: Scheduler_cancelCallback,

} = Scheduler;



// Remove the task node from the list

function unstable_cancelCallback(task{

  // Get the next node of callbackNode

  var next = task.next;

  // If next is null, the node no longer exists in the list

  if (next === null) {

    // Already cancelled.

    return;

  }

  // callback is the only node in the list

  / / firstTask firstDelayedTask should be similar to the concept of the cursor, which was about to perform the node

  if (task === next) {

    // Null to delete the callback node

    / / reset firstTask/firstDelayedTask

    if (task === firstTask) {

      firstTask = null;

    } else if (task === firstDelayedTask) {

      firstDelayedTask = null;

    }

  } else {

    / / will be firstTask/firstDelayedTask pointed to the next node

    if (task === firstTask) {

      firstTask = next;

    } else if (task === firstDelayedTask) {

      firstDelayedTask = next;

    }

    var previous = task.previous;

    // Delete the existing callbackNode

    previous.next = next;

    next.previous = previous;

  }



  task.next = task.previous = null;

}

Copy the code

Parse: Manipulate the Schedule list to “remove” the callback that is about to be executed, pointing the cursor to the next scheduled task

ScheduleSyncCallback () is used to queue a synchronous task and return the task to its temporary queue

Source:

// Queue callback and return the temporary queue

export function scheduleSyncCallback(callback: SchedulerCallback{

  // Push this callback into an internal queue. We'll flush these either in

  // the next tick, or earlier if something calls `flushSyncCallbackQueue`.

  // Refresh the callback queue the next time the refresh synchronization callback queue is scheduled or called



  // If the synchronization queue is empty, initialize the synchronization queue.

  // And refresh the queue at the beginning of the next schedule

  if (syncQueue === null) {

    syncQueue = [callback];

    // Flush the queue in the next tick, at the earliest.

    immediateQueueCallbackNode = Scheduler_scheduleCallback(

      // Grant scheduling immediate execution with high permissions

      Scheduler_ImmediatePriority,

      flushSyncCallbackQueueImpl,

    );

  }

  Callback is queued if the synchronization queue is not empty

  else {

    // Push onto existing queue. Don't need to schedule a callback because

    // we already scheduled one when we created the queue.

    // There is no need to dispatch the callback when joining the queue, because the callback is already scheduled when the queue is created

    syncQueue.push(callback);

  }

  //fake means temporary queue

  return fakeCallbackNode;

}

Copy the code

Scheduler_scheduleCallback() is called when the synchronization queue is empty, and the callback task is queued, and the callback task is wrapped as newTask and assigned to root.callbacknode

Scheduler_scheduleCallback () :

const {

  unstable_scheduleCallback: Scheduler_scheduleCallback,

} = Scheduler;



// Returns the wrapped task

function unstable_scheduleCallback(priorityLevel, callback, options{

  var currentTime = getCurrentTime();



  var startTime;

  var timeout;



  // Update startTime (default now) and timeout (default 5s)

  if (typeof options === 'object'&& options ! = =null) {

    var delay = options.delay;

    if (typeof delay === 'number' && delay > 0) {

      startTime = currentTime + delay;

    } else {

      startTime = currentTime;

    }

    timeout =

      typeof options.timeout === 'number'

        ? options.timeout

        : timeoutForPriorityLevel(priorityLevel);

  } else {

    // Times out immediately

    // var IMMEDIATE_PRIORITY_TIMEOUT = -1;

    // Eventually times out

    // var USER_BLOCKING_PRIORITY = 250;

    // The expiration time of the normal priority is 5s

    // var NORMAL_PRIORITY_TIMEOUT = 5000;

    // The expiration time of the low priority is 10s

    // var LOW_PRIORITY_TIMEOUT = 10000;



    timeout = timeoutForPriorityLevel(priorityLevel);

    startTime = currentTime;

  }

  // The expiration time is the current time +5s

  var expirationTime = startTime + timeout;

  // Encapsulate it into a new task

  var newTask = {

    callback,

    priorityLevel,

    startTime,

    expirationTime,

    nextnull.

    previousnull.

  };

  // If the time to start scheduling has been missed

  if (startTime > currentTime) {

    // This is a delayed task.

    // Insert the deferred callback into the deferred queue

    insertDelayedTask(newTask, startTime);

    // If the first task in the delay queue does not exist and the first task in the delay queue happens to be a new task,

    // Indicates that all tasks are deferred, and the task is the first deferred task

    if (firstTask === null && firstDelayedTask === newTask) {

      // All tasks are delayed, and this is the task with the earliest delay.

      // If the delay start flag is true, the scheduled time is cancelled

      if (isHostTimeoutScheduled) {

        // Cancel an existing timeout.

        cancelHostTimeout();

      }

      // Otherwise set to true

      else {

        isHostTimeoutScheduled = true;

      }

      // Schedule a timeout.

      requestHostTimeout(handleTimeout, startTime - currentTime);

    }

  }

  // If there is no delay, the task is inserted as planned

  else {

    insertScheduledTask(newTask, expirationTime);

    // Schedule a host callback, if needed. If we're already performing work,

    // wait until the next time we yield.

    // Update the flag for scheduling execution

    if(! isHostCallbackScheduled && ! isPerformingWork) {

      isHostCallbackScheduled = true;

      requestHostCallback(flushWork);

    }

  }

  // Returns the wrapped task

  return newTask;

}

Copy the code

When there is a new update, React will be updated after 5s by default.

Scheduler_scheduleCallback() has the following functions: Set the current time startTime and expirationTime timeout. ② create a newTask (including callback and expirationTime). ③ put newTask into the delay scheduling queue. ⑤ Return the wrapped newTask to the normal scheduling queue

(2) Queue this callback when the synchronization queue is not empty

ScheduleSyncCallback () eventually returns to the temporary callback point.


ScheduleCallback () wraps callback and updates the status of the scheduling queue if the task is asynchronous

Source:

// Wrap the callback and update the status of the scheduling queue

export function scheduleCallback(

  reactPriorityLevel: ReactPriorityLevel,

  callback: SchedulerCallback,

  options: SchedulerCallbackOptions | void | null,

{

  // Get the scheduling priority

  const priorityLevel = reactPriorityToSchedulerPriority(reactPriorityLevel);

  return Scheduler_scheduleCallback(priorityLevel, callback, options);

}

Copy the code

Scheduler_scheduleCallback() is called to queue the callback task, wrap the callback as newTask, and assign it to root.callbacknode

Func. Bind (xx) means that this in func is bound to xx, which means xx calls the func method

Attention! Func.bind (xx) This is just a bind, not a call!

Func. Bind (xx)()

At this point,scheduleCallbackForRoot()Analysis completed (8 to 11)

So here we are:

export function scheduleUpdateOnFiber(){  

  xxx  

  xxx  

  xxx  

  if (expirationTime === Sync) {      

    if(first render){

      xxx

    }else{      

      /* The content of the eight to eleven lectures */ 

      scheduleCallbackForRoot(root, ImmediatePriority, Sync);    

      // When there is no update

      if (executionContext === NoContext) {

        // Refresh the synchronization task queue

        flushSyncCallbackQueue();

      }

    }  

  }

}

Copy the code

FlushSyncCallbackQueue () Updates the status of the synchronization task queue

Source:

// Refresh the synchronization task queue

export function flushSyncCallbackQueue({

  // If the current node exists, interrupt the current node task and remove the task node from the list

  if(immediateQueueCallbackNode ! = =null) {

    Scheduler_cancelCallback(immediateQueueCallbackNode);

  }

  // Update the synchronization queue

  flushSyncCallbackQueueImpl();

}

Copy the code

FlushSyncCallbackQueueImpl () :

// Update the synchronization queue

function flushSyncCallbackQueueImpl({

  // If the synchronization queue is not updated and the synchronization queue is not empty

  if(! isFlushingSyncQueue && syncQueue ! = =null) {

    // Prevent re-entrancy.

    // Prevents repeated execution, which is equivalent to a lock

    isFlushingSyncQueue = true;

    let i = 0;

    try {

      const isSync = true;

      const queue = syncQueue;

      // Traverse the synchronization queue and update the status of the refresh isSync=true

      runWithPriority(ImmediatePriority, () => {

        for (; i < queue.length; i++) {

          let callback = queue[i];

          do {

            callback = callback(isSync);

          } while(callback ! = =null);

        }

      });

      // Set to null after traversal

      syncQueue = null;

    } catch (error) {

      // If something throws, leave the remaining callbacks on the queue.

      if(syncQueue ! = =null) {

        syncQueue = syncQueue.slice(i + 1);

      }

      // Resume flushing in the next tick

      Scheduler_scheduleCallback(

        Scheduler_ImmediatePriority,

        flushSyncCallbackQueue,

      );

      throw error;

    } finally {

      isFlushingSyncQueue = false;

    }

  }

}

Copy the code

Resolution: the current scheduling task is interrupted, first to “remove” from the list of the current node, and call the flushSyncCallbackQueueImpl update synchronous queue () task

Loop through the syncQueue and update the isSync status of the node (isSync=true)


And then here it is:

export function scheduleUpdateOnFiber(){  

  xxx  

  xxx  

  xxx  

  if (expirationTime === Sync) {      

    if(first render){

      xxx

    }else{      

      /* The content of the eight to eleven lectures */ 

      scheduleCallbackForRoot(root, ImmediatePriority, Sync);    

      if (executionContext === NoContext) {

        // Lecture 12

        flushSyncCallbackQueue();

      }

    }  

  }

  // If the task is asynchronous, the task is executed immediately

  // If (expirationTime === Sync)

  else {

    scheduleCallbackForRoot(root, priorityLevel, expirationTime);

  }

    if (

(executionContext & DiscreteEventContext) ! == NoContext &&

    // Only updates at user-blocking priority or greater are considered

    // discrete, even inside a discrete event.

    // Only if the user blocks updates of priority or higher are considered discrete, even in discrete events

    (priorityLevel === UserBlockingPriority ||

      priorityLevel === ImmediatePriority)

  ) {

    // This is the result of a discrete event. Track the lowest priority

    // discrete update per root so we can flush them early, if needed.

    // This is the result of discrete events. Track the lowest-priority discrete updates for each root so that we can clear them as early as needed.

    / / if rootsWithPendingDiscreteUpdates is null, the initialize it

    if (rootsWithPendingDiscreteUpdates === null) {

      // If key is root, value is expirationTime

      rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]);

    } else {

      // Get the latest DiscreteTime

      const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);

      / / update the DiscreteTime

      if (lastDiscreteTime === undefined || lastDiscreteTime > expirationTime) {

        rootsWithPendingDiscreteUpdates.set(root, expirationTime);

      }

    }

  }



}

Copy the code

ScheduleUpdateOnFiber () is a discrete time update before scheduling ends.


ScheduleWork flow chart

Making: github.com/AttackXiaoJ…


(after)