In the previous process, we have already understood Fiber, which can help us understand optimization-level scheduling mechanisms and other optimization application performance. Then we will further deepen the optimization of React through Hooks and functional components.
Processing flow of functional components
beginWork stage
We start directly from beginWork and explain the update and construction of the most commonly used functional components. Source code address
switch (workInProgress.tag) {
// ignore code
// Construction of child nodes of functional components
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,
renderLanes,
);
}
// ignore codeIn the beginWork phase, React will select the corresponding processing method based on the component type. For functional components, it will call the updateFunctionComponent method.
Update analysis
The main task of the beginWork exploration phase is to set whether the status needs to be updated. This state is the basis for many subsequent code judgments.
if (current !== null) {
//Double cache mechanism
const oldProps = current.memoizedProps
const newProps = workInProgress.pendingProps
// If not equal, enter comparison
if (oldProps !== newProps || hasLegacyContextChanged() || (__DEV__ ? workInProgress.type !== current.type : false)) {
// The status mark needs to update this status. Remember that many subsequent tasks related to this status will be related to it.
didReceiveUpdate = true
// If the rendering lane does not contain updates, there is no need to update
} else if (!includesSomeLane(renderLanes, updateLanes)) {
didReceiveUpdate = false
//Context special processing, optimization
switch (workInProgress.tag) {
}
// Does the loop child node need to be updated?
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes)
} else {
if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
//To handle the problem that the Suspense component does not update when the Context is updated, force the mark to become true.
didReceiveUpdate = true
} else {
// An update was scheduled on this fiber, but there are no new props
// nor legacy context. Set this to false. If an update queue or context
// consumer produces a changed value, it will set this to true. Otherwise,
// the component will assume the children have not changed and bail out.
didReceiveUpdate = false
}
}
} else {
// The rest does not need to be updated
didReceiveUpdate = false
}This code mainly does the following things:
- Compare whether the old and new props are equal
- Check whether the context has changed
- Set the
didReceiveUpdatestatus according to the comparison result - Handle special situations, such as forced updates of Suspense components
updateFunctionComponent
After setting the update state, React will call the updateFunctionComponent method to handle the functional component.
function updateFunctionComponent(
current,
workInProgress,
Component,
nextProps: any,
renderLanes,
) {
// ignore code
let context;
let nextChildren;
// This is related to useContext. Let’s talk about it in the hooks branch.
prepareToReadContext(workInProgress, renderLanes);
if (__DEV__) {
//Ignore code
} else {
//Here we directly enter the hooks related logic, and finally return the lower-level ReactElement object.
nextChildren = renderWithHooks(
current,
workInProgress,
Component,
nextProps,
context,
renderLanes,
);
}
//Old rule, if the status of didReceiveUpdate is that it does not need to be updated as mentioned earlier, search down the subsection.
if (current !== null && !didReceiveUpdate) {
bailoutHooks(current, workInProgress, renderLanes);
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
// React DevTools reads this flag. We will skip the performance stuff for now
workInProgress.flags |= PerformedWork;
// Generate child fiber nodes downwards
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}The main work of updateFunctionComponent includes:
- Prepare context
- Call
renderWithHooksto execute the function component - Determine whether an update is required based on the status of
didReceiveUpdate - Generate sub-Fiber nodes
renderWithHooks
The renderWithHooks function is the core of the Hooks mechanism, which provides state management and side effect processing capabilities for functional components:
export function renderWithHooks<Props, SecondArg>(
current: Fiber | null,
workInProgress: Fiber,
Component: (p: Props, arg: SecondArg) => any,
props: Props,
secondArg: SecondArg,
nextRenderLanes: Lanes,
): any {
// Phase 1: Set the global status
renderLanes = nextRenderLanes; // Current rendering priority
currentlyRenderingFiber = workInProgress; //The current fiber node, which is the fiber node corresponding to the function component
if (__DEV__) {
//Ignore code
}
// Clear the legacy status of the current fiber
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
workInProgress.lanes = NoLanes;
if (__DEV__) {
//Ignore code
} else {
// Phase 2: Generate ReactElement child nodes
// ReactCurrentDispatcher comes from the global variable in react-dom, confirm whether it is initialized or updated
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
?HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
//Execute function function
let children = Component(props, secondArg);
}
//Reset global variables and return
// After executing the function, restore the modified global variables without affecting the next call.
renderLanes = NoLanes;
currentlyRenderingFiber = (null: any);
currentHook = null;
workInProgressHook = null;
didScheduleRenderPhaseUpdate = false;
return children;
}The workflow of this function can be divided into three stages:
- Preparation phase: Set global variables and clear old status.
- Execution phase: Depending on whether it is the first rendering or update, select the appropriate Hooks processor, and then execute the component function.
- Cleanup phase: Reset global variables to prevent affecting other components.
Execution process analysis
Through breakpoint analysis, we can understand the execution process of function components:
Initialization phase:
- When the component first enters
beginWork, it is marked asindeterminateComponent(indeterminate component type) - Execute
renderWithHooks - After execution, mark
FunctionComponent
- When the component first enters
Update phase:
- Directly enter the
updateFunctionComponentlogic  
- Directly enter the
Summary
This article details the construction and update process of React functional components, from beginWork to updateFunctionComponent, and then to renderWithHooks. We learned how React handles functional components, how to decide whether to update, and the core implementation of the Hooks mechanism.
The next article will delve into the implementation of specific Hooks in function components and how to optimize performance.