Workinprogress. tag: FunctionComponent workinprogress. tag: FunctionComponent workinprogress. tag: FunctionComponent workinprogress. tag: FunctionComponent workinprogress. tag: FunctionComponent workinprogress. tag: FunctionComponent workinprogress. tag: FunctionComponent workinprogress. tag: FunctionComponent workinprogress. tag
/ / FunctionComponent updates
case FunctionComponent: {
//React component type, FunctionComponent type is function, ClassComponent type is class
const Component = workInProgress.type;
// Next render the props to be updated
const unresolvedProps = workInProgress.pendingProps;
// pendingProps
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
/ / update the FunctionComponent
// You can see that most of these are attributes of workInProgress
// The variable is defined to "freeze" the properties of workInProgress in function
return updateFunctionComponent(
//workInProgress.alternate
current,
workInProgress,
//workInProgress.type
Component,
//workInProgress.pendingProps
resolvedProps,
renderExpirationTime,
);
}
Copy the code
How does the FunctionComponent update
UpdateFunctionComponent Performs FunctionComponent updates
Source:
/ / update the functionComponent
/ / current: workInProgress. Alternate
/ / Component: workInProgress. Type
/ / resolvedProps: workInProgress pendingProps
function updateFunctionComponent(
current,
workInProgress,
Component,
nextProps: any,
renderExpirationTime,
) {
// Delete dev code
// The context will be explained later
const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
const context = getMaskedContext(workInProgress, unmaskedContext);
let nextChildren;
// Do not read the update tag
prepareToReadContext(workInProgress, renderExpirationTime);
prepareToReadEventComponents(workInProgress);
// Delete dev code
// Perform some operations on the hook functions used in the rendering process
nextChildren = renderWithHooks(
current,
workInProgress,
Component,
nextProps,
context,
renderExpirationTime,
);
// If it is not the first render and no updates are received
//didReceiveUpdate: optimizations on updates
if(current ! = =null && !didReceiveUpdate) {
bailoutHooks(current, workInProgress, renderExpirationTime);
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
}
// React DevTools reads this flag.
// Indicates that the current component was updated during rendering
workInProgress.effectTag |= PerformedWork;
// Change the ReactElement into a fiber object and update it to generate the corresponding DOM instance and mount it to the real DOM node
reconcileChildren(
current,
workInProgress,
nextChildren,
renderExpirationTime,
);
return workInProgress.child;
}
Copy the code
Most of the parameters passed to the updateFunctionComponent are properties of the Fiber object workInProgress
Why not just pass in a workInProgress object? My own guess is to “freeze” these properties outside to prevent them from being modified in updateFunctionComponent()
In the updateFunctionComponent(), two functions are executed: renderWithHooks() and reconcileChildren().
After executing these two methods, you finally return the workinProgress.Child, which is the first child of the Fiber object being updated
(3) bailoutOnAlreadyFinishedWork workLoop of parse () in the React source code has been parsed, its role is to skip all child nodes on the node and the node updates
BailoutHooks () ¶ bailoutHooks() ¶
// Skip the hooks update
export function bailoutHooks(
current: Fiber,
workInProgress: Fiber,
expirationTime: ExpirationTime,
) {
workInProgress.updateQueue = current.updateQueue;
workInProgress.effectTag &= ~(PassiveEffect | UpdateEffect);
// set NoWork to no update
if (current.expirationTime <= expirationTime) {
current.expirationTime = NoWork;
}
}
Copy the code
Do something about the hooks function used in rendering
Source:
// Perform some operations on the hook functions used in the rendering process
export function renderWithHooks(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
props: any,
refOrContext: any,
nextRenderExpirationTime: ExpirationTime,
) :any {
renderExpirationTime = nextRenderExpirationTime;
// The fiber object currently being rendered
currentlyRenderingFiber = workInProgress;
// State of the first time
nextCurrentHook = current ! = =null ? current.memoizedState : null;
// Delete dev code
// The following should have already been reset
// currentHook = null;
// workInProgressHook = null;
// remainingExpirationTime = NoWork;
// componentUpdateQueue = null;
// didScheduleRenderPhaseUpdate = false;
// renderPhaseUpdates = null;
// numberOfReRenders = 0;
// sideEffectTag = 0;
// TODO Warn if no hooks are used at all during mount, then some are used during update.
// Currently we will identify the update render as a mount because nextCurrentHook === null.
// This is tricky because it's valid for certain types of components (e.g. React.lazy)
// Using nextCurrentHook to differentiate between mount/update only works if at least one stateful hook is used.
// Non-stateful hooks (e.g. context) don't get added to memoizedState,
// so nextCurrentHook would be null during updates and mounts.
// Delete dev code
// Call HooksDispatcherOnMount for the first render
// Multiple render calls to HooksDispatcherOnUpdate
UseState, useEffect, and other hook functions
ReactCurrentDispatcher.current =
nextCurrentHook === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
// workinprogress. type = function // workinprogress. type = function
let children = Component(props, refOrContext);
// Determine if there is a scheduled update during render execution
// When there is an update to render
if (didScheduleRenderPhaseUpdate) {
do {
// Set to false to indicate that the loop is executed only once
didScheduleRenderPhaseUpdate = false;
// The number of nodes in fiber during re-rendering
numberOfReRenders += 1;
// Start over from the beginning of the list
// Record state to re-execute several useState functions inside the FunctionComponent
nextCurrentHook = current ! = =null ? current.memoizedState : null;
nextWorkInProgressHook = firstWorkInProgressHook;
// Release the current state
currentHook = null;
workInProgressHook = null;
componentUpdateQueue = null;
if (__DEV__) {
// Also validate hook order for cascading updates.
hookTypesUpdateIndexDev = - 1;
}
//HooksDispatcherOnUpdate
ReactCurrentDispatcher.current = __DEV__
? HooksDispatcherOnUpdateInDEV
: HooksDispatcherOnUpdate;
children = Component(props, refOrContext);
} while (didScheduleRenderPhaseUpdate);
renderPhaseUpdates = null;
numberOfReRenders = 0;
}
// We can assume the previous dispatcher is always this one, since we set it
// at the beginning of the render phase and there's no re-entrancy.
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
// Define a new fiber object
const renderedWork: Fiber = (currentlyRenderingFiber: any);
// Assign a value to the attribute
renderedWork.memoizedState = firstWorkInProgressHook;
renderedWork.expirationTime = remainingExpirationTime;
renderedWork.updateQueue = (componentUpdateQueue: any);
renderedWork.effectTag |= sideEffectTag;
if (__DEV__) {
renderedWork._debugHookTypes = hookTypesDev;
}
// This check uses currentHook so that it works the same in DEV and prod bundles.
// hookTypesDev could catch more cases (e.g. context) but only in DEV bundles.
const didRenderTooFewHooks =
currentHook ! = =null&& currentHook.next ! = =null;
/ / reset
renderExpirationTime = NoWork;
currentlyRenderingFiber = null;
currentHook = null;
nextCurrentHook = null;
firstWorkInProgressHook = null;
workInProgressHook = null;
nextWorkInProgressHook = null;
if (__DEV__) {
currentHookNameInDev = null;
hookTypesDev = null;
hookTypesUpdateIndexDev = - 1;
}
remainingExpirationTime = NoWork;
componentUpdateQueue = null;
sideEffectTag = 0;
// These were reset above
// didScheduleRenderPhaseUpdate = false;
// renderPhaseUpdates = null;
// numberOfReRenders = 0;
invariant(
! didRenderTooFewHooks,
'Rendered fewer hooks than expected. This may be caused by an accidental ' +
'early return statement.'.
);
return children;
}
Copy the code
Use useState() and useEffect Hook apis instead of using setState when FunctionComponent is used to write React components
So when updating FunctionComponent, renderWithHooks() is executed first to handle these hooks
(1) nextCurrentHook is assigned according to current, so nextCurrentHook can also be used to determine whether it is the first rendering of the component
(2) Both HooksDispatcherOnMount and HooksDispatcherOnUpdate are objects of useState, useEffect and other hook functions:
const HooksDispatcherOnMount: Dispatcher = {
readContext,
useCallback: mountCallback,
useContext: readContext,
useEffect: mountEffect,
useImperativeHandle: mountImperativeHandle,
useLayoutEffect: mountLayoutEffect,
useMemo: mountMemo,
useReducer: mountReducer,
useRef: mountRef,
useState: mountState,
useDebugValue: mountDebugValue,
useEvent: updateEventComponentInstance,
};
const HooksDispatcherOnUpdate: Dispatcher = {
readContext,
useCallback: updateCallback,
useContext: readContext,
useEffect: updateEffect,
useImperativeHandle: updateImperativeHandle,
useLayoutEffect: updateLayoutEffect,
useMemo: updateMemo,
useReducer: updateReducer,
useRef: updateRef,
useState: updateState,
useDebugValue: updateDebugValue,
useEvent: updateEventComponentInstance,
};
Copy the code
As you can see, each Hook API corresponds to an updated method, which we will discuss later
(3) let children = Component(props, refOrContext); Component is workinprogress. type. It can be function or class, but I didn’t think I could call Component(props, refOrContext) as a method.
So I don’t know what children are at the moment, and I’ll mention it in the “preface” if I find anything new.
(4) then when the didScheduleRenderPhaseUpdate is true, perform a while loop, the loop, will save the state of the state, and reset the hooks, components, update the queue is null, Finally, execute Component(props, refOrContext) again to get the new children
DidScheduleRenderPhaseUpdate:
// Whether an update was scheduled during the currently executing render pass.
// Determine if there is a scheduled update during render execution
let didScheduleRenderPhaseUpdate: boolean = false;
Copy the code
This cycle, one of my doubt is, while will didScheduleRenderPhaseUpdate set to false, so the cycle will only perform a, why want to use the while? Why not use if… The else?
There is no answer yet
(5) Define a new Fiber object to keep some variables from which hooks are fixed, and finally set all variables to null, return children
Functions of the reconcileChildren: The ReactElement is turned into a Fiber object and updated to generate corresponding INSTANCES of the DOM, which are mounted to real DOM nodes
Source:
export function reconcileChildren(
current: Fiber | null,
workInProgress: Fiber,
nextChildren: any,
renderExpirationTime: ExpirationTime,
) {
if (current === null) {
// If this is a fresh new component that hasn't been rendered yet, we
// won't update its child set by applying minimal side-effects. Instead,
// we will add them all to the child before it gets rendered. That means
// we can optimize this reconciliation pass by not tracking side-effects.
// Because it is the first rendering, there is no current-child, so the second argument is passed null
//React first renders the order of the parent nodes, then the child nodes
workInProgress.child = mountChildFibers(
workInProgress,
null.
nextChildren,
renderExpirationTime,
);
} else {
// If the current child is the same as the work in progress, it means that
// we haven't yet started any work on these children. Therefore, we use
// the clone algorithm to create a copy of all the current children.
// If we had any progressed work already, that is invalid at this point so
// let's throw it out.
workInProgress.child = reconcileChildFibers(
workInProgress,
current.child,
nextChildren,
renderExpirationTime,
);
}
}
Copy the code
The mountChildFibers() and reconcileChildFibers() call the same function ChildReconciler:
//true false
export const reconcileChildFibers = ChildReconciler(true);
export const mountChildFibers = ChildReconciler(false);
Copy the code
False indicates the first rendering,true vice versa
The ChildReconciler reconcileChildren()
This method is more than 1,100 lines, preceded by the definitions of function and finally returned to reconcileChildFibers, so we look from back to front
Source:
// Whether to track side effects
function ChildReconciler(shouldTrackSideEffects) {
xxx
xxx
xxx
function reconcileChildFibers() :Fiber | null {
}
return reconcileChildFibers;
}
Copy the code
ShouldTrackSideEffects =true; shouldTrackSideEffects=true; shouldTrackSideEffects=true
This is too long. Look at the reconcileChildFibers at the end
Functions for each reconcileChildFibers: For different types of nodes, there are different node operations
Source:
// This API will tag the children with the side-effect of the reconciliation
// itself. They will be added to the side-effect list as we pass through the
// children and the parent.
function reconcileChildFibers(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
//The newly calculated children
newChild: any,
expirationTime: ExpirationTime,
) :Fiber | null {
// This function is not recursive.
// If the top level item is an array, we treat it as a set of children,
// not as a fragment. Nested arrays on the other hand will be treated as
// fragment nodes. Recursion happens at the normal flow.
// Handle top level unkeyed fragments as if they were arrays.
// This leads to an ambiguity between <>{[...] } and <>... < / a >.
// We treat the ambiguous cases above the same.
const isUnkeyedTopLevelFragment =
typeof newChild === 'object' &&
newChild ! = =null &&
// Write
{arr.map((a,b)=> XXX)}
, which is called REACT_FRAGMENT_TYPE
newChild.type === REACT_FRAGMENT_TYPE &&
newChild.key === null;
// A type of REACT_FRAGMENT_TYPE does not require any updates, just render the child nodes
if (isUnkeyedTopLevelFragment) {
newChild = newChild.props.children;
}
// Handle object types
const isObject = typeof newChild === 'object'&& newChild ! = =null;
/ / element node
if (isObject) {
switch (newChild.?typeof) {
/ / ReactElement node
case REACT_ELEMENT_TYPE:
return placeSingleChild(
reconcileSingleElement(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
),
);
//ReactDOM.createPortal(child, container)
//https://zh-hans.reactjs.org/docs/react-dom.html#createportal
case REACT_PORTAL_TYPE:
return placeSingleChild(
reconcileSinglePortal(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
),
);
}
}
// Text node
if (typeof newChild === 'string' || typeof newChild === 'number') {
return placeSingleChild(
reconcileSingleTextNode(
returnFiber,
currentFirstChild,
' ' + newChild,
expirationTime,
),
);
}
// Array node
if (isArray(newChild)) {
return reconcileChildrenArray(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
);
}
//IteratorFunction
if (getIteratorFn(newChild)) {
return reconcileChildrenIterator(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
);
}
// An error is reported if the above element requirements are not met
if (isObject) {
throwOnInvalidObjectType(returnFiber, newChild);
}
// Delete dev code
// Display a warning
if (typeof newChild === 'undefined' && !isUnkeyedTopLevelFragment) {
// If the new child is undefined, and the return fiber is a composite
// component, throw an error. If Fiber return types are disabled,
// we already threw above.
// That is, workInProgress, the node being updated
switch (returnFiber.tag) {
case ClassComponent: {
// Delete dev code
}
// Intentionally fall through to the next case, which handles both
// functions and classes
// eslint-disable-next-lined no-fallthrough
case FunctionComponent: {
const Component = returnFiber.type;
invariant(
false.
'%s(...) : Nothing was returned from render. This usually means a ' +
'return statement is missing. Or, to render nothing, ' +
'return null.'.
Component.displayName || Component.name || 'Component'.
);
}
}
}
// Remaining cases are all treated as empty.
// If the old node exists but the updated node is null, the contents of the old node need to be deleted
return deleteRemainingChildren(returnFiber, currentFirstChild);
}
Copy the code
Analytic: (1) isUnkeyedTopLevelFragment wrote such as when we are in development
<div>{ arr.map((a,b)= >xxx) }</div>
Copy the code
REACT_FRAGMENT_TYPE is evaluated as REACT_FRAGMENT_TYPE, and React renders its child nodes directly:
newChild = newChild.props.children;
Copy the code
If element type is object, there are two cases: REACT_ELEMENT_TYPE (ReactElement node); The other is REACT_PORTAL_TYPE, the portal node, which is commonly used in dialogs, hover cards, and prompt boxes. See the official document “Portals”
With REACT_ELEMENT_TYPE, the reconcileSingleElement method is implemented
③ If it is a text node, the reconcileSingleTextNode method is implemented
④ If the last deleteRemainingChildren command is executed, the node to be updated is null, and the content of the original node needs to be deleted
As you can see, the reconcileChildFibers method in ChildReconciler performs different operation node functions based on the node type of the new node newChild
In our next article, we will talk about reconcileSingleElement, reconcileSingleTextNode and deleteRemainingChildren
Making: ReactFiberBeginWork
ReactFiberHooks
ReactChildFiber
(after)