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,
next: null.
previous: null.
};
// 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)