The React update task mainly calls a workLoop called workLoop to build the workInProgress tree. The construction process is divided into two stages: down traversal and up traceback. The beginWork and completeWork are performed on each node of the path.
The beginWork mentioned in this article is the entry point to process node updates, and it calls different processing functions depending on the type of fiber node.
React to the beginWork operation of each node, first check whether the node and its subtree are updated, then generate new Fiber after calculating the new state and diff, then flag the effectTag on the new Fiber. Finally, return its child node so that the beginWork continues for the child node. If it has no children, it returns null, indicating that the node is the end node and can be traced up to the completeWork stage.
Click on theAccess the React source debug repository.
The work flow of beginWork is shown in the figure below, which simplifies the process. Only the App node is processed by beginWork, and the process of other nodes is similar
Duties and responsibilities
It can be seen from the overview that the overall work of the beginWork stage is to update nodes and return subtrees, but the real beginWork function is only the entrance of node update and will not directly update operations. As an entry point, its role is obvious, intercepting nodes that do not need to be updated. At the same time, it’s going to push the context onto the stack (beginWork on the stack, completeWork off the stack), so I don’t care about it for now.
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes
) :Fiber | null {
// Get workinprogress. lanes to determine whether the node needs to be updated by determining whether it is empty
const updateLanes = workInProgress.lanes;
// Determine whether current is mounted for the first time or for subsequent updates based on whether current exists
/ / if it is updated, see first priority is enough, enough to call bailoutOnAlreadyFinishedWork
// Reuse the fiber node to get out of the current node.
if(current ! = =null) {
const oldProps = current.memoizedProps;
const newProps = workInProgress.pendingProps;
if( oldProps ! == newProps || hasLegacyContextChanged() ) { didReceiveUpdate =true;
} else if(! includesSomeLane(renderLanes, updateLanes)) {// No update is required at this time
didReceiveUpdate = false;
switch (workInProgress.tag) {
case HostRoot:
...
case HostComponent:
...
case ClassComponent:
...
case HostPortal:
...
}
// Intercept nodes that do not need updating
returnbailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes); }}else {
didReceiveUpdate = false;
}
// The code goes here to indicate that it really needs to deal with nodes, depending on the type of fiber
// call their respective handlers
// Delete lanes on the workInProgress node.
// It is reassigned after updateQueue is processed
workInProgress.lanes = NoLanes;
// Handle node updates according to different node types
switch (workInProgress.tag) {
case IndeterminateComponent:
...
case LazyComponent:
...
case FunctionComponent:
...
return updateFunctionComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
case ClassComponent:
...
return updateClassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
case HostRoot:
return updateHostRoot(current, workInProgress, renderLanes);
case HostComponent:
return updateHostComponent(current, workInProgress, renderLanes);
case HostText:
returnupdateHostText(current, workInProgress); . }}Copy the code
As you can see, once the node enters the beginWork, will first whether you need to identify the node processing, if not processing, call the bailoutOnAlreadyFinishedWork reuse node, otherwise truly to update.
How can update and initialization be distinguished
Check whether current exists.
The first step is to understand what current is, based on the rule of double buffering, scheduling updates with two trees, the Current Tree shown on the screen and the workInProgress Tree being built in the background based on the current Tree. Then, current and workInProgress can be understood as the relationship of mirror images. The workInProgress node currently traversed by the workLoop is from the child node of the parent fiber of its corresponding current node (i.e., the current node), so the workInProgress node and current node are also mirrors.
If it is the first rendering, there is no current node for the specific workInProgress node. If it is in the update process, since the current node has been generated during the first rendering, the workInProgress node has a corresponding current node.
The final decision to create fiber or Diff Fiber depends on whether the node is first rendered or updated. However, during the update, if the priority of the node was insufficient, the existing node would be reused directly, that is, the logic of bailout instead of the following update logic.
Multiplexing node process
A node that is reusable means that it does not need to be updated. In beginWork code above you can see, if the priority of the node does not meet the requirements, it is not updated, will call bailoutOnAlreadyFinishedWork function, to reuse the current node as the new workInProgress node of the tree.
The beginWork function intercepts logic that does not need to update the node
if(! includesSomeLane(renderLanes, updateLanes)) { ...// No update is required at this time. Intercept nodes that do not need to be updated
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
Copy the code
BeginWork its return value has two cases:
- Return the child node of the current node, and then continue with beginWork with this child node as the next unit of work, continuously generate fiber nodes, and build the workInProgress tree.
- Return null, the traversal of the current Fiber subtree terminates, and the completeWork goes back from the current Fiber node.
BailoutOnAlreadyFinishedWork function return values.
- Return the child node of the current node. The precondition is that the child node of the current node has been updated, and the current node can be reused directly without processing. The process of reuse is to copy a child node of the current node and return it.
- Returns NULL if the current child node is not updated and traversal of the current subtree terminates. Start completeWork.
From this function, we can also realize that it is important to recognize whether the subtree of the current Fiber node is updated or not, which can determine whether to terminate the traversal of the current Fiber subtree and reduce the complexity directly. If childLanes are not empty, it indicates that there are nodes in the subtree that need to be updated, so you need to continue down.
The process of marking Fiber. ChildLanes occurs at the beginning of dispatch, inmarkUpdateLaneFromFiberToRootIn the function
With that in mind, take a look at the source code to understand the reuse process:
function bailoutOnAlreadyFinishedWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes
) :Fiber | null {
if(current ! = =null) {
workInProgress.dependencies = current.dependencies;
}
// The flag has a skipped update
markSkippedUpdateLanes(workInProgress.lanes);
// If the child node is not updated, null is returned to terminate the traversal
if(! includesSomeLane(renderLanes, workInProgress.childLanes)) {return null;
} else {
// The child node is updated, so copy the child node from current and return
cloneChildFibers(current, workInProgress);
returnworkInProgress.child; }}Copy the code
conclusion
The main function of the beginWork is to process the fiber currently traversed, then return its sub-fibers after processing, and ejects the fiber nodes one by one. Then the workInProgress tree will be built bit by bit.
This is the general beginWork process, but in fact, the core update work is in each update function, which will arrange fiber node into two processing processes in turn: calculation of new state and Diff algorithm. Due to space limitation, these two contents will be explained in detail in two articles, which can be continued attention.