RenderRoot () calls workLoop()/workLoopSync() to update loop units:
function renderRoot(){
xxx
xxx
if(workInProgress ! = =null) {
// Perform updates for each node
if (isSync) {
workLoopSync();
} else {
// Determine whether to continue calling performUnitOfWork
workLoop();
}
}
xxx
xxx
}
Copy the code
This article explains workLoop and its internal method logic
Note: This article deals with some properties of the Fiber object. See RootFiber for details on React source code
The loop executes performUnitOfWork and assigns the value to workInProgress until the value of workInProgress is empty. The loop is terminated
Source:
// Synchronize the workLoop, indicating that it cannot be interrupted
function workLoopSync() {
// Already timed out, so perform work without checking if we need to yield.
while(workInProgress ! = =null) {
workInProgress = performUnitOfWork(workInProgress);
}
}
// Asynchronous workLoop, which can be broken
// Determine whether to continue calling performUnitOfWork
function workLoop() {
// Perform work until Scheduler asks us to yield
/ * nextUnitOfWork = "workInProgress * /
// The root node is not reached
// With workinprogress. child, loop until all nodes are updated
while(workInProgress ! = =null && !shouldYield()) {
workInProgress = performUnitOfWork(workInProgress);
}
}
Copy the code
WorkInProgress is a Fiber object, and a non-null value means that there are still updates to be performed on the Fiber object
2, performUnitOfWork function: call beginWork, from parent to child, component (node) update; Call completeUnitOfWork, child to parent, and do some processing on the node based on the effectTag
Source:
// Navigate through and manipulate nodes from top to bottom, and then operate nodes from bottom to top according to effectTag
UnitOfWork (workInProgress) is a fiber object
function performUnitOfWork(unitOfWork: Fiber) :Fiber | null {
// The current, flushed, state of this fiber is the alternate. Ideally
// nothing should rely on this, but relying on it here means that we don't
// need an additional field on the work in progress.
//current <=> workInProgress
// Get the current node
const current = unitOfWork.alternate;
// Make a mark on unitOfWork
startWorkTimer(unitOfWork);
//dev environment
setCurrentDebugFiberInDEV(unitOfWork);
let next;
if(enableProfilerTimer && (unitOfWork.mode & ProfileMode) ! == NoMode) {
startProfilerTimer(unitOfWork);
// Perform node operations and create child nodes
//current: workInProgress.alternate
//unitOfWork: workInProgress
//workInProgress.child
// Check whether fiber is updated. If fiber is updated, update corresponding components. If fiber is not updated, replicate nodes
next = beginWork(current, unitOfWork, renderExpirationTime);
stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
} else {
next = beginWork(current, unitOfWork, renderExpirationTime);
}
/ / to watch
resetCurrentDebugFiberInDEV();
// Replace the props to be updated with the props being used
unitOfWork.memoizedProps = unitOfWork.pendingProps;
// Indicates that the lowest leaf node has been updated, and the sibling of the leaf node has been traversed
if (next === null) {
// If this doesn't spawn new work, complete the current work.
// After traversing from top to bottom, the completeUnitOfWork does some processing from bottom to top according to the effact tag
next = completeUnitOfWork(unitOfWork);
}
ReactCurrentOwner.current = null;
return next;
}
Copy the code
Resolution: (2) Execute beginWork(Current, unitOfWork, renderExpirationTime) to update components. Assign the value returned to next (3). Replace the props to be updated on unitOfWork with the props to be used (4). If next is null, completeUnitOfWork is executed, traversing from bottom to top, (5) Finally return to Next (next is the Fiber object)
Look at the beginWork (). CompleteUnitOfWork () will be parsed in a later article.
BeginWork notice: switch… Case… Skip the two long paragraphs
Function: Check whether fiber is updated. If fiber is updated, the corresponding components will be updated. If fiber is not updated, the nodes will be copied
Source:
// Check whether fiber is updated. If fiber is updated, update corresponding components. If fiber is not updated, replicate nodes
//current: workInProgress.alternate
function beginWork(
current: Fiber | null,
// The child node created by workInProgress is also workInProgress
workInProgress: Fiber,
// Marks the highest priority point in the render
renderExpirationTime: ExpirationTime,
): Fiber | null {
// Only when react.domRender is called does the expirationTime of rootFiber have a value and rootFiber is updated
// Get the expiration time of updates on the Fiber object
const updateExpirationTime = workInProgress.expirationTime;
// Determine if it is the first render
// If not the first render
if(current ! == null) {
// Props after the last rendering, i.e. OldProps
const oldProps = current.memoizedProps;
// The new changes bring props, that is, newProps
const newProps = workInProgress.pendingProps;
if (
// Whether the front and back props are not equal
oldProps ! == newProps ||
// Whether an older context is used and has changed
hasLegacyContextChanged() ||
// Force a re-render if the implementation changed due to hot reload:
// Development environment is always false
(__DEV__ ? workInProgress.type! == current.type : false)
) {
// If props or context changed, mark the fiber as having performed work.
// This may be unset if the props are determined to be equal later (memo).
// Determine that an update is received
didReceiveUpdate = true;
}
// There is an update, but the priority is not high, it does not need to be executed during this rendering, set to false
else if (updateExpirationTime < renderExpirationTime) {
didReceiveUpdate = false;
// This fiber does not have any pending work. Bailout without entering
// the begin phase. There's still some bookkeeping we that needs to be done
// in this optimized path, mostly pushing stuff onto the stack.
// Update the corresponding component according to the workInProgress tag
switch (workInProgress.tag) {
case HostRoot:
pushHostRootContext(workInProgress);
resetHydrationState();
break;
case HostComponent:
pushHostContext(workInProgress);
if (
workInProgress.mode & ConcurrentMode &&
renderExpirationTime ! == Never &&
shouldDeprioritizeSubtree(workInProgress.type, newProps)
) {
if (enableSchedulerTracing) {
markSpawnedWork(Never);
}
// Schedule this fiber to re-render at offscreen priority. Then bailout.
workInProgress.expirationTime = workInProgress.childExpirationTime = Never;
return null;
}
break;
case ClassComponent: {
const Component = workInProgress.type;
if (isLegacyContextProvider(Component)) {
pushLegacyContextProvider(workInProgress);
}
break;
}
case HostPortal:
pushHostContainer(
workInProgress,
workInProgress.stateNode.containerInfo,
);
break;
case ContextProvider: {
const newValue = workInProgress.memoizedProps.value;
pushProvider(workInProgress, newValue);
break;
}
case Profiler:
if (enableProfilerTimer) {
workInProgress.effectTag |= Update;
}
break;
case SuspenseComponent: {
const state: SuspenseState | null = workInProgress.memoizedState;
constdidTimeout = state ! == null;
if (didTimeout) {
// If this boundary is currently timed out, we need to decide
// whether to retry the primary children, or to skip over it and
// go straight to the fallback. Check the priority of the primary
// child fragment.
const primaryChildFragment: Fiber = (workInProgress.child: any);
const primaryChildExpirationTime =
primaryChildFragment.childExpirationTime;
if (
primaryChildExpirationTime ! == NoWork &&
primaryChildExpirationTime >= renderExpirationTime
) {
// The primary children have pending work. Use the normal path
// to attempt to render the primary children again.
return updateSuspenseComponent(
current,
workInProgress,
renderExpirationTime,
);
} else {
pushSuspenseContext(
workInProgress,
setDefaultShallowSuspenseContext(suspenseStackCursor.current),
);
// The primary children do not have pending work with sufficient
// priority. Bailout.
const child = bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
if(child ! == null) {
// The fallback children have pending work. Skip over the
// primary children and work on the fallback.
return child.sibling;
} else {
return null;
}
}
} else {
pushSuspenseContext(
workInProgress,
setDefaultShallowSuspenseContext(suspenseStackCursor.current),
);
}
break;
}
case DehydratedSuspenseComponent: {
if (enableSuspenseServerRenderer) {
pushSuspenseContext(
workInProgress,
setDefaultShallowSuspenseContext(suspenseStackCursor.current),
);
// We know that this component will suspend again because if it has
// been unsuspended it has committed as a regular Suspense component.
// If it needs to be retried, it should have work scheduled on it.
workInProgress.effectTag |= DidCapture;
}
break;
}
case SuspenseListComponent: {
const didSuspendBefore =
(current.effectTag & DidCapture) ! == NoEffect;
const childExpirationTime = workInProgress.childExpirationTime;
if (childExpirationTime < renderExpirationTime) {
// If none of the children had any work, that means that none of
// them got retried so they'll still be blocked in the same way
// as before. We can fast bail out.
pushSuspenseContext(workInProgress, suspenseStackCursor.current);
if (didSuspendBefore) {
workInProgress.effectTag |= DidCapture;
}
return null;
}
if (didSuspendBefore) {
// If something was in fallback state last time, and we have all the
// same children then we're still in progressive loading state.
// Something might get unblocked by state updates or retries in the
// tree which will affect the tail. So we need to use the normal
// path to compute the correct tail.
return updateSuspenseListComponent(
current,
workInProgress,
renderExpirationTime,
);
}
// If nothing suspended before and we're rendering the same children,
// then the tail doesn't matter. Anything new that suspends will work
// in the "together" mode, so we can continue from the state we had.
let renderState = workInProgress.memoizedState;
if(renderState ! == null) {
// Reset to the "together" mode in case we've started a different
// update in the past but didn't complete it.
renderState.rendering = null;
renderState.tail = null;
}
pushSuspenseContext(workInProgress, suspenseStackCursor.current);
break;
}
case EventComponent:
if (enableFlareAPI) {
pushHostContextForEventComponent(workInProgress);
}
break;
}
// Skip the update of this node and all its children
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
}
} else {
didReceiveUpdate = false;
}
// Before entering the begin phase, clear the expiration time.
workInProgress.expirationTime = NoWork;
// If the node is updated
// Update components based on node type
switch (workInProgress.tag) {
case IndeterminateComponent: {
return mountIndeterminateComponent(
current,
workInProgress,
workInProgress.type.
renderExpirationTime,
);
}
case LazyComponent: {
const elementType = workInProgress.elementType;
return mountLazyComponent(
current,
workInProgress,
elementType,
updateExpirationTime,
renderExpirationTime,
);
}
/ / FunctionComponent updates
case FunctionComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateFunctionComponent(
current,
workInProgress,
Component,
resolvedProps,
renderExpirationTime,
);
}
/ / ClassComponent updates
case ClassComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateClassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderExpirationTime,
);
}
case HostRoot:
return updateHostRoot(current, workInProgress, renderExpirationTime);
case HostComponent:
return updateHostComponent(current, workInProgress, renderExpirationTime);
case HostText:
return updateHostText(current, workInProgress);
case SuspenseComponent:
return updateSuspenseComponent(
current,
workInProgress,
renderExpirationTime,
);
case HostPortal:
return updatePortalComponent(
current,
workInProgress,
renderExpirationTime,
);
case ForwardRef: {
const type = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === type
? unresolvedProps
: resolveDefaultProps(type, unresolvedProps);
return updateForwardRef(
current,
workInProgress,
type.
resolvedProps,
renderExpirationTime,
);
}
case Fragment:
return updateFragment(current, workInProgress, renderExpirationTime);
case Mode:
return updateMode(current, workInProgress, renderExpirationTime);
case Profiler:
return updateProfiler(current, workInProgress, renderExpirationTime);
case ContextProvider:
return updateContextProvider(
current,
workInProgress,
renderExpirationTime,
);
case ContextConsumer:
return updateContextConsumer(
current,
workInProgress,
renderExpirationTime,
);
case MemoComponent: {
const type = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
// Resolve outer props first, then resolve inner props.
let resolvedProps = resolveDefaultProps(type, unresolvedProps);
if (__DEV__) {
if (workInProgress.type! == workInProgress.elementType) {
const outerPropTypes = type.propTypes;
if (outerPropTypes) {
checkPropTypes(
outerPropTypes,
resolvedProps, // Resolved for outer only
'prop'.
getComponentName(type),
getCurrentFiberStackInDev,
);
}
}
}
resolvedProps = resolveDefaultProps(type.type, resolvedProps);
return updateMemoComponent(
current,
workInProgress,
type.
resolvedProps,
updateExpirationTime,
renderExpirationTime,
);
}
case SimpleMemoComponent: {
return updateSimpleMemoComponent(
current,
workInProgress,
workInProgress.type.
workInProgress.pendingProps,
updateExpirationTime,
renderExpirationTime,
);
}
case IncompleteClassComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return mountIncompleteClassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderExpirationTime,
);
}
case DehydratedSuspenseComponent: {
if (enableSuspenseServerRenderer) {
return updateDehydratedSuspenseComponent(
current,
workInProgress,
renderExpirationTime,
);
}
break;
}
case SuspenseListComponent: {
return updateSuspenseListComponent(
current,
workInProgress,
renderExpirationTime,
);
}
case EventComponent: {
if (enableFlareAPI) {
return updateEventComponent(
current,
workInProgress,
renderExpirationTime,
);
}
break;
}
}
invariant(
false.
'Unknown unit of work tag. This error is likely caused by a bug in ' +
'React. Please file an issue.'.
);
}
Copy the code
MemoizedProps and pendingProps are my expirationTime memoizedProps and my memoizedProps are my expirationTime memoizedProps. React RootFiber (3) switch… Context updateExpirationTime < renderExpirationTime = context It means that the current fiber update priority is not high, in the process of the rendering will not perform its update, so will perform bailoutOnAlreadyFinishedWork, the renewal of the node and all child nodes to skip, The workinprogress. tag is used to update the workinprogress. tag
Four, bailoutOnAlreadyFinishedWork role: skip all child nodes on the node and the node updates
Source:
// Skip the update of this node and all its children
function bailoutOnAlreadyFinishedWork(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
): Fiber | null {
/ / to watch
cancelWorkTimer(workInProgress);
if(current ! = =null) {
// Reuse previous dependencies
workInProgress.dependencies = current.dependencies;
}
if (enableProfilerTimer) {
// Don't update "base" render times for bailouts.
stopProfilerTimerIfRunning(workInProgress);
}
// Check if the children have any pending work.
//expirationTime indicates whether this node has updates. If this node has updates, it may affect the updates of child nodes
// If both expirationTime and childExpirationTime are not present, then the subtree does not need to be updated
// Updates due to descendant nodes
const childExpirationTime = workInProgress.childExpirationTime;
If the subtree does not need to be updated, null is returned
// One of the benefits of childExpirationTime is that it is quick to know if a subtree is up to date, so that unupdated subtrees can be skipped
// If childExpirationTime is empty, React also needs to traverse the subtree to see if it is updated
if (childExpirationTime < renderExpirationTime) {
// The children don't have any work either. We can skip them.
// TODO: Once we add back resuming, we should check if the children are
// a work-in-progress set. If so, we need to transfer their effects.
// Skip the update rendering of the entire subtree, which is a very big optimization
return null;
}
// Harmonize child nodes
else {
// This fiber doesn't have work, but its subtree does. Clone the child
// fibers and continue.
// This node does not need to be updated, nor does the child node, so just copy the child node over
cloneChildFibers(current, workInProgress);
return workInProgress.child;
}
}
Copy the code
Resolution: Normally, child updates are evaluated by traversal of the child tree, but React cleverly sets the childExpirationTime when the child updates, and eventually sets the childExpirationTime with the highest priority on the parent node. If the childExpirationTime priority is less than renderExpirationTime, the subtree traversal and update rendering are skipped.
This is a very big optimization.
CompleteUnitOfWork has a similar structure to beginWork, but I will cover it in a later article
WorkLoop Flow chart
Seven, making github.com/AttackXiaoJ…
(after)