This is the second day of my participation in the August More text Challenge. For details, see:August is more challenging
This article is based on the latest stable version V17.0.2
An overview of
Through the introduction of the macro package structure and the two major working cycles above, we have a certain understanding of the React-Reconciler package.
The main functions of the React-Reconciler package are summarized here, and the main functions are divided into four aspects:
- Input: Exposure
api
Functions (e.g.scheduleUpdateOnFiber
) to supply other packages (e.greact
Package). - Registering scheduling tasks: With the scheduling center (
scheduler
Package) to register for scheduling taskstask
, waiting for the task callback. - Execution task callback: constructed in memory
Fiber tree
, while working with the renderer (react-dom
) in memory to create andfiber
The correspondingDOM
Node. - Output: With the renderer (
react-dom
) interaction, renderingDOM
Node.
The above source code is concentrated in ReactFiberWorkloop.js. Now string these functions together (from input to output), as shown in the following figure:
The 1,2,3, and 4 steps in the figure reflect the react-Reconciler package’s process from input to output, which is a fixed process that runs every time it is updated.
decomposition
Only the core function call relationships are listed in the diagram (each step has its own implementation details, which will be explored in subsequent sections). Break down each of the four steps to understand their main logic.
The input
In reactFiberWorkloop. js, the only function that accepts input is the scheduleUpdateOnFiber source address. In the React-Reconciler’s exposed API, scheduleUpdateOnFiber is invoked indirectly whenever an operation (either first rendering or subsequent update) is required to change the fiber. So the scheduleUpdateOnFiber function is the only way in the input link.
// The only function that receives input signals
export function scheduleUpdateOnFiber(fiber: Fiber, lane: Lane, eventTime: number,) {
/ /... Omit some irrelevant code
const root = markUpdateLaneFromFiberToRoot(fiber, lane);
if (lane === SyncLane) {
if( (executionContext & LegacyUnbatchedContext) ! == NoContext && (executionContext & (RenderContext | CommitContext)) === NoContext ) {// Direct 'fiber structure'
performSyncWorkOnRoot(root);
} else {
// Register the scheduling task, which is scheduled by the 'Scheduler' package, and then 'fiber construct' indirectlyensureRootIsScheduled(root, eventTime); }}else {
// Register the scheduling task, which is scheduled by the 'Scheduler' package, and then 'fiber construct' indirectlyensureRootIsScheduled(root, eventTime); }}Copy the code
ScheduleUpdateOnFiber ();
- No scheduling, direct
Fiber structure
. - Register the scheduling task, after
Scheduler
Package scheduling, indirectFiber structure
.
Registering a Scheduling Task
Tightly connected to the input link, the “scheduleUpdateOnFiber” function, and then immediately go to the ensureRootIsScheduled function:
/ /... Omit some irrelevant code
function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
// The first half: determine whether a new schedule needs to be registered
const existingCallbackNode = root.callbackNode;
const nextLanes = getNextLanes(
root,
root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
);
const newCallbackPriority = returnNextLanesPriority();
if (nextLanes === NoLanes) {
return;
}
if(existingCallbackNode ! = =null) {
const existingCallbackPriority = root.callbackPriority;
if (existingCallbackPriority === newCallbackPriority) {
return;
}
cancelCallback(existingCallbackNode);
}
// Second half: register the scheduling task
let newCallbackNode;
if (newCallbackPriority === SyncLanePriority) {
newCallbackNode = scheduleSyncCallback(
performSyncWorkOnRoot.bind(null, root),
);
} else if (newCallbackPriority === SyncBatchedLanePriority) {
newCallbackNode = scheduleCallback(
ImmediateSchedulerPriority,
performSyncWorkOnRoot.bind(null, root),
);
} else {
const schedulerPriorityLevel = lanePriorityToSchedulerPriority(
newCallbackPriority,
);
newCallbackNode = scheduleCallback(
schedulerPriorityLevel,
performConcurrentWorkOnRoot.bind(null, root),
);
}
root.callbackPriority = newCallbackPriority;
root.callbackNode = newCallbackNode;
}
Copy the code
The logic of ensureRootIsScheduled is clear, and it’s divided into 2 parts:
- The first half: Determines whether a new schedule needs to be registered (exits the function if no new schedule is needed)
- Second half: Register the scheduling task
performSyncWorkOnRoot
orperformConcurrentWorkOnRoot
Is encapsulated into the task callback (scheduleCallback
)- Waiting for the scheduling center to execute the task, task running is actually execution
performSyncWorkOnRoot
orperformConcurrentWorkOnRoot
Perform a task callback
Task callback, is actually perform performSyncWorkOnRoot or performConcurrentWorkOnRoot. Taking a quick look at their source code (which we’ll delve into in the Fiber Tree construction section), you can strip out the main logic, and the amount of code for a single function isn’t much.
performSyncWorkOnRoot:
/ /... Omit some irrelevant code
function performSyncWorkOnRoot(root) {
let lanes;
let exitStatus;
lanes = getNextLanes(root, NoLanes);
// 1. Fiber tree structure
exitStatus = renderRootSync(root, lanes);
// 2. Exception handling: An exception may occur during the construction of the fiber
if(root.tag ! == LegacyRoot && exitStatus === RootErrored) {// ...
}
// 3. Output: Render the Fiber tree
const finishedWork: Fiber = (root.current.alternate: any);
root.finishedWork = finishedWork;
root.finishedLanes = lanes;
commitRoot(root);
// Before exiting, check again to see if there are any other updates and whether a new schedule needs to be initiated
ensureRootIsScheduled(root, now());
return null;
}
Copy the code
The logic of performSyncWorkOnRoot is clear and divided into three parts:
- Fiber tree structure
- Exception handling: It is possible that an exception occurred during the construction of fiber
- Call the output
performConcurrentWorkOnRoot
/ /... Omit some irrelevant code
function performConcurrentWorkOnRoot(root) {
const originalCallbackNode = root.callbackNode;
// 1. Refresh the effects of pending state. It is possible that some effects will cancel the task
const didFlushPassiveEffects = flushPassiveEffects();
if (didFlushPassiveEffects) {
if(root.callbackNode ! == originalCallbackNode) {// The task is cancelled, exit the call
return null;
} else {
// Current task was not canceled. Continue.}}// 2. Get the priority of the render
let lanes = getNextLanes(
root,
root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
);
// 3. Construct the fiber tree
let exitStatus = renderRootConcurrent(root, lanes);
if (
includesSomeLane(
workInProgressRootIncludedLanes,
workInProgressRootUpdatedLanes,
)
) {
// If a new update is generated in Render, and the priority of the new update intersects with the priority of the original Render
// Then the initial render is invalid, discarding the initial render result and waiting for the next dispatch
prepareFreshStack(root, NoLanes);
} else if(exitStatus ! == RootIncomplete) {// 4. Exception handling: An exception may occur during the construction of the fiber
if (exitStatus === RootErrored) {
// ...
}.
const finishedWork: Fiber = (root.current.alternate: any);
root.finishedWork = finishedWork;
root.finishedLanes = lanes;
// 5. Output: Render the Fiber tree
finishConcurrentRender(root, exitStatus, lanes);
}
// Before exiting, check again to see if there are any other updates and whether a new schedule needs to be initiated
ensureRootIsScheduled(root, now());
if (root.callbackNode === originalCallbackNode) {
/ / rendering is blocked, return a new performConcurrentWorkOnRoot function, wait for the next call
return performConcurrentWorkOnRoot.bind(null, root);
}
return null;
}
Copy the code
PerformConcurrentWorkOnRoot logic and performSyncWorkOnRoot differs, support for the interruptible rendering:
- call
performConcurrentWorkOnRoot
Function, first check to see if therender
Whether to restore the last rendering. - If the rendering is interrupted, and finally return a new performConcurrentWorkOnRoot function, wait for the next call.
The output
commitRoot
:
/ /... Omit some irrelevant code
function commitRootImpl(root, renderPriorityLevel) {
// Set local variables
const finishedWork = root.finishedWork;
const lanes = root.finishedLanes;
// Clear the FiberRoot object properties
root.finishedWork = null;
root.finishedLanes = NoLanes;
root.callbackNode = null;
// Commit phase
let firstEffect = finishedWork.firstEffect;
if(firstEffect ! = =null) {
const prevExecutionContext = executionContext;
executionContext |= CommitContext;
// Stage 1: before the DOM mutation
nextEffect = firstEffect;
do {
commitBeforeMutationEffects();
} while(nextEffect ! = =null);
// Stage 2: DOM mutation, interface change
nextEffect = firstEffect;
do {
commitMutationEffects(root, renderPriorityLevel);
} while(nextEffect ! = =null);
root.current = finishedWork;
// Phase 3: Layout phase, calling life cycle componentDidUpdate and callback functions
nextEffect = firstEffect;
do {
commitLayoutEffects(root, lanes);
} while(nextEffect ! = =null);
nextEffect = null;
executionContext = prevExecutionContext;
}
ensureRootIsScheduled(root, now());
return null;
}
Copy the code
In the output phase, the implementation logic of commitRoot is in the commitRootImpl function, where the main logic is to process the side effect queue and reflect the latest fiber tree structure to the DOM.
The core logic is divided into three steps:
commitBeforeMutationEffects
- Before DOM changes, the main processing side effects queue with
Snapshot
.Passive
Of the tagfiber
Node.
- Before DOM changes, the main processing side effects queue with
commitMutationEffects
- The DOM changes and the interface is updated. Main processing side effects with queue
Placement
.Update
.Deletion
.Hydrating
Of the tagfiber
Node.
- The DOM changes and the interface is updated. Main processing side effects with queue
commitLayoutEffects
- After dom changes, the main side effects are processed in the queue with
Update | Callback
Of the tagfiber
Node.
- After dom changes, the main side effects are processed in the queue with
conclusion
This section analyzes the operation process of the Reconciler from a macro perspective and divides it into four steps, which basically covers the core logic of the React-Reconciler package.
Write in the last
This article belongs to the diagram react source code series in the operation of the core plate, this series of nearly 20 articles, not for the interview, really is to understand the React source code, and then improve the architecture and coding ability.
The first draft of the graphic section has been completed and will be updated in August. If there are any errors in the article, we will correct them as soon as possible on Github.