“This is the 25th day of my participation in the Gwen Challenge in November. See details: The Last Gwen Challenge in 2021”
The purpose of today’s tutorial is to analyze how renderWithHooks and bailoutHooks are implemented. These are, unsurprisingly, the key parts of how React performs render updates.
renderWithHooks
It occurred to me that I seemed to have written about renderWithHooks before, Portals. But it’s not complete either, so here’s something to add.
export function renderWithHooks<Props, SecondArg> (
current: Fiber | null,
workInProgress: Fiber,
Component: (p: Props, arg: SecondArg) => any,
props: Props,
secondArg: SecondArg,
nextRenderLanes: Lanes,
): any {}
Copy the code
The return type is any, which gives no useful information, but it doesn’t matter, as we can see in the implementation code that its return value is the return value of the last call to Component(props, secondArg).
The Component argument we passed to renderWithHooks is workinProgress. type, the user-defined functional Component function. Remember separately that in the functional component definition, we return XML tags defined in JSX syntax that will be compiled into the react.createElement function. So our renderWithHooks return the value of react. createElement, which is something like this:
Const element = {$$typeof: REACT_ELEMENT_TYPE, // elment tags, such as div, span type: type, key: Key, ref: ref, props: props, // the component responsible for creating this element _owner: owner,};Copy the code
The current parameter is workinprogress.alternate.
In the Component function we might use the useEffect function. How is this implemented?
UseEffect -> updateEffect -> updateEffectImpl for useEffect -> updateEffectImpl
function updateEffectImpl (fiberFlags, hookFlags, create, deps): void { const hook = updateWorkInProgressHook() const nextDeps = deps === undefined ? null : deps let destroy = undefined if (currentHook ! == null) { const prevEffect = currentHook.memoizedState destroy = prevEffect.destroy if (nextDeps ! == null) { const prevDeps = prevEffect.deps if (areHookInputsEqual(nextDeps, prevDeps)) { hook.memoizedState = pushEffect(hookFlags, create, destroy, nextDeps) return } } } currentlyRenderingFiber.flags |= fiberFlags hook.memoizedState = pushEffect( HookHasEffect | hookFlags, create, destroy, nextDeps, ) }Copy the code
PushEffect returns an Effect object with the following structure:
export type Effect = {|
tag: HookFlags,
create: () => (() => void) | void,
destroy: (() => void) | void,
deps: Array<mixed> | null,
next: Effect,
|}
Copy the code
The destroy callback is the return value of the CREATE callback. The implementation logic of pushEffect is also simple, as follows:
- Creating an Effect object
- Check currentlyRenderingFiber updateQueue, if is null, the call createFunctionComponentUpdateQueue to instantiate a updateQueue
- UpdateQueue is a circular single linked list structure that stores the lastEffect pointer lastEffect, adds an Effect object to the end of the list, and updates the pointer to lastEffect
At this point, it became clear that fiber’s update value stored the reconcileChildren side effect code defined by the user with the useEffect, and we wanted to focus on when the update value would be executed, perhaps in relation to the reconcileChildren.
reconcileChildren
It updates the workinProgress. child, with possible side effects.
export function reconcileChildren (
current: Fiber | null,
workInProgress: Fiber,
nextChildren: any,
renderLanes: Lanes,
) {
if (current === null) {
workInProgress.child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderLanes,
)
} else {
workInProgress.child = reconcileChildFibers(
workInProgress,
current.child,
nextChildren,
renderLanes,
)
}
}
Copy the code
The reconcileChildFibers are signed below
function reconcileChildFibers(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChild: any,
lanes: Lanes,
): Fiber | null {
Copy the code
If newChild is a fragment node with a null key (see fragments-react), then newChild = newchild.props. Children;
If newChild is a non-empty object, different operations are performed according to newChild.$$typeof. If newChild is an Element node ($$typeof = REACT_ELEMENT_TYPE), The function returns a placeSingleChild call, which is a reconcileSingleElement call.
bailoutHooks
The core implementation is as follows:
export function bailoutHooks(
current: Fiber,
workInProgress: Fiber,
lanes: Lanes,
) {
workInProgress.updateQueue = current.updateQueue;
workInProgress.flags &= ~(PassiveEffect | UpdateEffect);
current.lanes = removeLanes(current.lanes, lanes);
}
Copy the code
That is, this function updates the updateQueue of workInProgress, clears the PassiveEffect and UpdateEffect flags, and removes lanes from current. Lanes.
Remember, how did we call this bailout function in the previous layer, the updateFunctionComponent function?
First, it is called after executing the renderWithHooks function, when current is not null and didReceiveUpdate is false, and second, it is called as follows:
bailoutHooks(current, workInProgress, renderLanes)
Copy the code
The update ue on the workInProgress alternate will be synchronized to the Update uE on the workInProgress alternate. Will workInProgress. UpdateQueue reduction for old updateQueue
\