The github project address is react-research
1 jsx
JSX syntax is compiled into functions by Babel, so all components in React are functions
/ / before compilation
function Main() {
return (
<div className="wrapper">
<h1>Hello World</h1>
<h1>ReactChild</h1>
</div>
);
}
Copy the code
/ / the compiled
function Main() {
return createElement(
"div",
{ className: "wrapper".ref: "main".key: "main" },
createElement("h1".null."Hello World"),
createElement("h2".null."ReactChild")); }Copy the code
React Element data structure
{
? typeof: Symbol(react.element),
key: "main".props: {
children: [{? typeof: Symbol(react.element),
key: null.props: { children: "Hello World" },
type: "h1"._owner: null
},
{
? typeof: Symbol(react.element),
key: null.props: { children: "ReactChild" },
type: "h1"._owner: null}].className: "wrapper"
},
ref: "main".type: "div"._owner: null
}
Copy the code
2 render
All React Elements are rendered into the real DOM using the reactdom. render method
// Call the procedure
render(element, document.querySelector("#body"));
Copy the code
function render(element, container, callback) {
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
false,
callback
);
}
Copy the code
2.1 legacyRenderSubtreeIntoContainer
function legacyRenderSubtreeIntoContainer(
parentComponent, // null
children, // ReactEelement
container, // DOM
forceHydrate, // false
callback
) {
let root = container._reactRootContainer;
let fiberRoot;
if(! root) {// Create fiber root
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate
);
fiberRoot = root._internalRoot;
// Non-batch update
unbatchedUpdates((a)= >{ updateContainer(children, fiberRoot, parentComponent, callback); }); }}Copy the code
2.1.1 legacyCreateRootFromDOMContainer
LegacyCreateRootFromDOMContainer main function is to return a FiberRoot hydrate of meaning is the meaning of water flooding, The purpose is to inject some interaction events into the (dry flat) DOM during server rendering. This article does not examine server rendering. All hydrate is false
function legacyCreateRootFromDOMContainer(container, forceHydrate) {
const shouldHydrate = forceHydrate;
if(! shouldHydrate) {let rootSibling;
// Remove all DOM from the root container
while((rootSibling = container.lastChild)) { container.removeChild(rootSibling); }}return createLegacyRoot(
container,
shouldHydrate
? {
hydrate: true,} :undefined
);
}
Copy the code
2.1.2 createLegacyRoot
The internal function createLegacyRoot is expanded
function createLegacyRoot(container, options) {
function ReactDOMBlockingRoot(container, tag, options) {
function FiberRootNode(containerInfo, tag, hydrate) {
this.tag = tag; / / 0
this.containerInfo = containerInfo; // #body
this.pendingChildren = null;
this.current = null; // FiberNode
this.pingCache = null;
this.finishedWork = null;
this.timeoutHandle = noTimeout; // -1
this.context = null;
this.pendingContext = null;
this.hydrate = hydrate; // false
this.callbackNode = null;
this.callbackId = NoLanes; / / 0
this.callbackIsSync = false;
this.expiresAt = - 1;
this.pendingLanes = NoLanes; / / 0
this.suspendedLanes = NoLanes;
this.pingedLanes = NoLanes;
this.expiredLanes = NoLanes;
this.mutableReadLanes = NoLanes;
this.finishedLanes = NoLanes;
}
function FiberNode(tag, pendingProps, key, mode) {
// Instance
this.tag = tag; / / 3
this.key = key; // null
this.elementType = null;
this.type = null;
this.stateNode = null;
// Fiber
this.return = null;
this.child = null;
this.sibling = null;
this.index = 0;
this.ref = null;
this.pendingProps = pendingProps; // null
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.dependencies_new = null;
this.mode = mode; / / 0
// Effects
this.effectTag = NoEffect; / / 0
this.nextEffect = null;
this.firstEffect = null;
this.lastEffect = null;
this.lanes = NoLanes; / / 0
this.childLanes = NoLanes;
this.alternate = null;
}
function initializeUpdateQueue(fiber) {
const queue = {
baseState: fiber.memoizedState,
firstBaseUpdate: null.lastBaseUpdate: null.shared: {
pending: null,},effects: null}; fiber.updateQueue = queue; }consthydrate = options ! =null && options.hydrate === true;
consthydrationCallbacks = (options ! =null && options.hydrationOptions) || null;
const root = new FiberRootNode(container, tag, hydrate);
// React mode
// NoMode = 0b00000;
// StrictMode = 0b00001;
// BlockingMode = 0b00010;
// ConcurrentMode = 0b00100;
// ProfileMode = 0b01000;
let mode;
// tag = LegacyRoot = 0 = NoMode
if (tag === ConcurrentRoot) {
// ConcurrentMode Parallel mode is the latest mode
/ / about parallel patterns: https://reactjs.org/docs/concurrent-mode-intro.html
/ / how to unlock the parallel mode: https://reactjs.org/docs/concurrent-mode-reference.html#createroot
mode = ConcurrentMode | BlockingMode | StrictMode;
} else if (tag === BlockingRoot) {
mode = BlockingMode | StrictMode;
} else {
mode = NoMode;
}
// HostRoot = 3
const uninitializedFiber = new FiberNode(HostRoot, null.null, mode);
root.current = uninitializedFiber;
uninitializedFiber.stateNode = root;
initializeUpdateQueue(uninitializedFiber);
// A random number is converted to 36 bits to generate a random string
const randomKey = Math.random().toString(36).slice(2);
container["__reactContainer$" + randomKey] = root.current;
this._internalRoot = root;
}
// LegacyRoot = 0
return new ReactDOMBlockingRoot(container, LegacyRoot, options);
}
Copy the code
Summary (Fiber data structures) : Understanding what each attribute does early on will help a lot later
2.2 unbatchedUpdates
Change the executionContext variable executionContext to go into non-batch update mode and revert to the previous mode after execution
// BatchedContext = 0b000001
// LegacyUnbatchedContext = 0b001000
// executionContext = 0b001000 = 8
function unbatchedUpdates(fn, a) {
const preExecutionContext = executionContext;
// Remove BatchedContext from the BatchedContext operator
executionContext &= ~BatchedContext; // 0b000000 & 0b111110
// Or the operator represents the type containing LegacyUnbatchedContext
executionContext |= LegacyUnbatchedContext; // 0b000000 | 0b001000
try {
return fn(a);
} finally {
executionContext = preExecutionContext;
if (executionContext === NoContext) {
FlushSyncCallbackQueue is not opened at the first time
// Refresh the destroy now function in the batch taskflushSyncCallbackQueue(); }}}Copy the code
2.3 updateContainer
function updateContainer(element, container, parentComponent, callback) {
const current = container.current;
const eventTime = requestEventTime();
const suspenseConfig = null;
const lane = requestUpdateLane(current, suspenseConfig); // SyncLane 1
// Get the context of the current node and its children
const context = getContextForSubtree(parentComponent); / / {}
if (container.context === null) {
container.context = context; // fiberRoot.context = {}
} else {
container.pendingContext = context;
}
// Create an update pair
const update = createUpdate(eventTime, lane, suspenseConfig);
update.payload = { element };
callback = callback === undefined ? null : callback;
if(callback ! = =null) {
update.callback = callback;
}
enqueueUpdate(container.current, update);
scheduleUpdateOnFiber(current, lane);
return lane;
}
Copy the code
2.3.1 requestEventTime
Gets the time the program has been running so far for prioritization
let currentEventTime = - 1;
let now;
const initialTime = Date.now();
if (typeof performance === "object" && typeof performance.now === "function") {
now = (a)= > performance.now();
} else {
now = (a)= > Date.now() - initialTime;
}
// executionContext = 0b001000
// RenderContext = 0b010000;
// CommitContext = 0b100000;
function requestEventTime() {
if((executionContext & (RenderContext | CommitContext)) ! == NoContext) {// We are inside React, so we can read the actual time
return now();
}
// We are not inside React, so we might be in the middle of browser events.
if(currentEventTime ! = =- 1) {
// Use the same start time for all updates until we enter React again
return currentEventTime;
}
// This is the first update since React was created. Calculate the new start time.
currentEventTime = now();
return currentEventTime;
}
Copy the code
2.3.2 requestUpdateLane
function requestUpdateLane(fiber, suspenseConfig) {
const mode = fiber.mode;
if ((mode & BlockingMode) === NoMode) {
// In old mode, lane is only SyncLane = 1
return SyncLane;
} else if ((mode & ConcurrentMode) === NoMode) {
return SyncBatchedLane;
} else if(! deferRenderPhaseUpdateToNextBatch && (executionContext & RenderContext) ! == NoContext && workInProgressRootRenderLanes ! == NoLane ) {return pickArbitraryLane(workInProgressRootRenderLanes);
}
if (currentEventWipLanes === NoLanes) {
currentEventWipLanes = workInProgressRootIncludedLanes;
}
let lane;
if(suspenseConfig ! = =null) {}else {
const schedulerPriority = getCurrentPriorityLevel();
if( (executionContext & DiscreteEventContext) ! == NoContext && schedulerPriority === UserBlockingPriority ) { lane = findUpdateLane(InputDiscreteLanePriority, currentEventWipLanes); }else {
constlanePriority = schedulerPriorityToLanePriority(schedulerPriority); lane = findUpdateLane(lanePriority, currentEventWipLanes); }}return lane;
}
Copy the code
2.3.3 getContextForSubtree
const emptyContextObject = {};
function getContextForSubtree(parentComponent) {
if(! parentComponent) {return emptyContextObject;
}
const fiber = parentComponent._reactInternals;
const disableLegacyContext = false;
function isContextProvider(type) {
if (disableLegacyContext) {
return false;
} else {
const childContextTypes = type.childContextTypes;
returnchildContextTypes ! = =null&& childContextTypes ! = =undefined; }}function findCurrentUnmaskedContext(fiber) {
if (disableLegacyContext) {
return emptyContextObject;
} else {
let node = fiber;
do {
switch (node.tag) {
case HostRoot:
return node.stateNode.context;
case ClassComponent: {
const Component = node.type;
if (isContextProvider(Component)) {
return node.stateNode.__reactInternalMemoizedMergedChildContext;
}
break;
}
}
node = node.return;
} while(node ! = =null); }}const parentContext = findCurrentUnmaskedContext(fiber);
function processChildContext(fiber, type, parentContext) {
if (disableLegacyContext) {
return parentContext;
} else {
const instance = fiber.stateNode;
const childContextTypes = type.childContextTypes;
if (typeofinstance.getChildContext ! = ="function") {
return parentContext;
}
const childContext = instance.getChildContext();
return { ...parentContext, ...childContext };
}
}
const isLegacyContextProvider = isContextProvider;
if (fiber.tag === ClassComponent) {
const Component = fiber.type;
if (isLegacyContextProvider(Component)) {
returnprocessChildContext(fiber, Component, parentContext); }}return parentContext;
}
Copy the code
2.3.4 createUpdate
You create an update that puts the children in the payload as an element and then you expand the children based on that update object
function createUpdate(eventTime, lane, suspenseConfig) {
const update = {
eventTime, / / 20.0000
lane, / / 1
suspenseConfig, // null
tag: UpdateState, / / 0
payload: null.callback: null.next: null};return update;
}
Copy the code
2.3.5 enqueueUpdate
function enqueueUpdate(fiber, update) {
const updateQueue = fiber.updateQueue;
if (updateQueue === null) {
// this only happens when the fiber node is unplugged
return;
}
const sharedQueue = updateQueue.shared;
const pending = sharedQueue.pending;
if (pending === null) {
// This is the first update to create a circular singly linked list
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
sharedQueue.pending = update;
}
Copy the code
2.3.6 scheduleUpdateOnFiber
const RootIncomplete = 0;
const RootSuspendedWithDelay = 4;
let workInProgressRootExitStatus = RootIncomplete;
function scheduleUpdateOnFiber(fiber, lane) {
function mergeLanes(a, b) {
return a | b;
}
function markRootUpdated(root, updateLane) {
// root.pendingLanes = NoLanes = 0
// updateLane = 1
root.pendingLanes |= updateLane; / / 1
// Now we use the same heuristic as the old ExpirationTimes model:
// Retry some lanes at the same or lower priority, but do not try to update those at higher priority
// But does not include lower-priority updates. This is good
// When considering updates of different priorities, but not sufficient to update within the same priority
// We want to process these updates in parallel
// Suspend has the same or lower priority for any updates.
const higherPriorityLanes = updateLane - 1; // Change 0b1000 to 0b0111
root.suspendedLanes &= higherPriorityLanes; / / 0
root.pingedLanes &= higherPriorityLanes; / / 0
}
function markUpdateLaneFromFiberToRoot(fiber, lane) {
// Update source fiber's lanes
fiber.lanes = mergeLanes(fiber.lanes, lane); / / 1
let alternate = fiber.alternate; // null
if(alternate ! = =null) {
// false
alternate.lanes = mergeLanes(alternate.lanes, lane);
}
let node = fiber.return; // null
let root = null;
if (node === null && fiber.tag === HostRoot) {
// true
root = fiber.stateNode; // RootFiber
} else {
while(node ! = =null) {
alternate = node.alternate;
node.childLanes = mergeLanes(node.childLanes, lane);
if(alternate ! = =null) {
alternate.childLanes = mergeLanes(alternate.childLanes, lane);
}
if (node.return === null && node.tag === HostRoot) {
root = node.stateNode;
break; } node = node.return; }}if(root ! = =null) {
// true
// mark root for pending updates.
markRootUpdated(root, lane);
if (workInProgressRoot === root) {
// false do not enter
if (
deferRenderPhaseUpdateToNextBatch ||
(executionContext & RenderContext) === NoContext
) {
workInProgressRootUpdatedLanes = mergeLanes(
workInProgressRootUpdatedLanes,
lane
);
}
if(workInProgressRootExitStatus === RootSuspendedWithDelay) { markRootSuspended(root, workInProgressRootRenderLanes); }}}return root;
}
const root = markUpdateLaneFromFiberToRoot(fiber, lane);
// Get the current priority
const priorityLevel = getCurrentPriorityLevel();
if (lane === SyncLane) {
if (
// Check if we are in non-batch update mode(executionContext & LegacyUnbatchedContext) ! == NoContext &&// Check to see if we are not rendering
(executionContext & (RenderContext | CommitContext)) === NoContext
) {
// Register interactions to be processed at root to avoid losing traced interaction data.
// Do not expand until executed
schedulePendingInteractions(root, lane);
// This is a legacy method used to initialize the installation of reactdom.render - Ed
// Root within batchedUpdates should be synchronized, but layout updates should be deferred until the batch ends
performSyncWorkOnRoot(root);
} else {
// First render does not enter
ensureRootIsScheduled(root);
schedulePendingInteractions(root, lane);
if (executionContext === NoContext) {
// Clear synchronization tasks immediately, unless we have entered batch.
// It is purposely placed in scheduleUpdateOnFiber instead of scheduleCallbackForFiber so that scheduling callbacks do not need to be immediately emptied.
// We only update for user-initiated actions to preserve the historical behavior of the older schema.flushSyncCallbackQueue(); }}}else {
/ /... Other patterns}}Copy the code
Summary:
3 performSyncWorkOnRoot
Real render entry
function performSyncWorkOnRoot(root) {
// First render does not enter
flushPassiveEffects();
let lanes;
let exitStatus;
if (
root === workInProgressRoot &&
includesSomeLane(root.expiredLanes, workInProgressRootRenderLanes)
) {
// This is an incomplete tree, and at least one lane has expired to finish rendering
// Finish rendering before rendering the rest of the overdue work.
lanes = workInProgressRootRenderLanes;
exitStatus = renderRootSync(root, lanes);
if (
includesSomeLane(
workInProgressRootIncludedLanes,
workInProgressRootUpdatedLanes
)
) {
// Render includes lanes updated during the render phase.
For example, when unhiding the hidden tree, we include all lanes
// Hide the previously skipped content of the tree. This Lanes is a superset of the Lanes we used when we started rendering.
// Note that this only happens when part of the tree is rendered
/ / at the same time. If the entire tree is rendered synchronously, there are no staggered events.lanes = getNextLanes(root, lanes); exitStatus = renderRootSync(root, lanes); }}else {
// First render here
lanes = getNextLanes(root, NoLanes); / / 1
exitStatus = renderRootSync(root, lanes);
}
if(root.tag ! == LegacyRoot && exitStatus === RootErrored) {// If something threw an error, try rendering one more time. We'll render
// synchronously to block concurrent data mutations, and we'll includes
// all pending updates are included. If it still fails after the second
// attempt, we'll give up and commit the resulting tree.
lanes = getLanesToRetrySynchronouslyOnError(root);
if (lanes !== NoLanes) {
exitStatus = renderRootSync(root, lanes);
}
}
if (exitStatus === RootFatalErrored) {
const fatalError = workInProgressRootFatalError;
prepareFreshStack(root, NoLanes);
markRootSuspended(root, lanes);
ensureRootIsScheduled(root);
throw fatalError;
}
// We now have a consistent tree. Because this is a sync render, we
// will commit it even if something suspended.
const finishedWork = root.current.alternate;
root.finishedWork = finishedWork;
root.finishedLanes = lanes;
commitRoot(root);
// Before exiting, make sure there's a callback scheduled for the next
// pending level.
ensureRootIsScheduled(root);
return null;
}
Copy the code
3.1 getNextLanes
function getNextLanes(root, wipLanes) {
const pendingLanes = root.pendingLanes; / / 1
if (pendingLanes === NoLanes) {
// false
return_highestLanePriority = NoLanePriority;
return NoLanes;
}
let nextLanes = NoLanes; / / 0
let nextLanePriority = NoLanePriority; / / 0
let equalOrHigherPriorityLanes = NoLanes; / / 0
const expiredLanes = root.expiredLanes; / / 0
const suspendedLanes = root.suspendedLanes; / / 0
const pingedLanes = root.pingedLanes; / / 0
// Check whether the task has expired
if(expiredLanes ! == NoLanes) {// false
nextLanes = expiredLanes;
nextLanePriority = return_highestLanePriority = SyncLanePriority;
equalOrHigherPriorityLanes = (getLowestPriorityLane(nextLanes) << 1) - 1;
} else {
// Do not perform any idle work, even if it is suspended, until all non-idle work has been completed.
// const NonIdleLanes = 0b0000111111111111111111111111111;
const nonIdlePendingLanes = pendingLanes & NonIdleLanes; / / 1
if(nonIdlePendingLanes ! == NoLanes) {// true
const nonIdleUnblockedLanes = nonIdlePendingLanes & ~suspendedLanes; / / 1
if(nonIdleUnblockedLanes ! == NoLanes) {// true
nextLanes = getHighestPriorityLanes(nonIdleUnblockedLanes); / / 1
nextLanePriority = return_highestLanePriority; // SyncLanePriority 16
equalOrHigherPriorityLanes = (1 << return_updateRangeEnd) - 1; / / 1
} else {
const nonIdlePingedLanes = nonIdlePendingLanes & pingedLanes;
if(nonIdlePingedLanes ! == NoLanes) { nextLanes = getHighestPriorityLanes(nonIdlePingedLanes); nextLanePriority = return_highestLanePriority; equalOrHigherPriorityLanes = (1 << return_updateRangeEnd) - 1; }}}else {
// There is only idle work left
const unblockedLanes = pendingLanes & ~suspendedLanes;
if(unblockedLanes ! == NoLanes) { nextLanes = getHighestPriorityLanes(unblockedLanes); nextLanePriority = return_highestLanePriority; equalOrHigherPriorityLanes = (1 << return_updateRangeEnd) - 1;
} else {
if(pingedLanes ! == NoLanes) { nextLanes = getHighestPriorityLanes(pingedLanes); nextLanePriority = return_highestLanePriority; equalOrHigherPriorityLanes = (1 << return_updateRangeEnd) - 1; }}}}if (nextLanes === NoLanes) {
// Only we are suspended can get here
return NoLanes;
}
// If there are lanes of higher priority, we will include them even if they are suspended
nextLanes = pendingLanes & equalOrHigherPriorityLanes; / / 1
If we were already in the middle of rendering, switching lanes would interrupt him and we would lose our progress
// We can only do this in the new higher priority lanes
if( wipLanes ! == NoLanes && wipLanes ! == nextLanes &&// If we have suspended for a period of time, then the interrupt is ok. Don't wait until the root is complete.
(wipLanes & suspendedLanes) === NoLanes
) {
getHighestPriorityLanes(wipLanes);
const wipLanePriority = return_highestLanePriority;
if (nextLanePriority <= wipLanePriority) {
return wipLanes;
} else{ return_highestLanePriority = nextLanePriority; }}return nextLanes; / / 1
}
Copy the code
3.1.1 getHighestPriorityLanes
const DefaultLanePriority = 9;
const SyncLanePriority = 16;
const SyncUpdateRangeEnd = 1;
let return_highestLanePriority = DefaultLanePriority;
let return_updateRangeEnd = - 1;
function getHighestPriorityLanes(lanes) {
if((SyncLane & lanes) ! == NoLanes) { return_highestLanePriority = SyncLanePriority; return_updateRangeEnd = SyncUpdateRangeEnd;return SyncLane; / / 1
}
// ...
}
Copy the code
3.2 renderRootSync
const ReactCurrentDispatcher = {
current: null};// React hooks
const ContextOnlyDispatcher = {
readContext,
useCallback: throwInvalidHookError,
useContext: throwInvalidHookError,
useEffect: throwInvalidHookError,
useImperativeHandle: throwInvalidHookError,
useLayoutEffect: throwInvalidHookError,
useMemo: throwInvalidHookError,
useReducer: throwInvalidHookError,
useRef: throwInvalidHookError,
useState: throwInvalidHookError,
useDebugValue: throwInvalidHookError,
useResponder: throwInvalidHookError,
useDeferredValue: throwInvalidHookError,
useTransition: throwInvalidHookError,
useMutableSource: throwInvalidHookError,
useOpaqueIdentifier: throwInvalidHookError,
};
function renderRootSync(root, lanes) {
function pushDispatcher(root) {
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
if (prevDispatcher === null) {
return ContextOnlyDispatcher;
} else {
returnprevDispatcher; }}const prevExecutionContext = executionContext;
// Switch to render execution context
executionContext |= RenderContext;
/ / hooks
const prevDispatcher = pushDispatcher(root);
// If root or lanes changes, discard the existing stack
// And prepare a new one, or we will continue to leave where we are
if(workInProgressRoot ! == root || workInProgressRootRenderLanes ! == lanes) {// Prepare a clone of the current Fiber node in the global variable workInProgress
prepareFreshStack(root, lanes);
// I don't know yet
startWorkOnPendingInteractions(root, lanes);
}
// I don't know yet
const prevInteractions = pushInteractions(root);
do {
try {
// Loop through workInProgress
workLoopSync();
break;
} catch(thrownValue) { handleError(root, thrownValue); }}while (true);
resetContextDependencies();
if (enableSchedulerTracing) {
popInteractions(((prevInteractions: any): Set<Interaction>));
}
executionContext = prevExecutionContext;
popDispatcher(prevDispatcher);
if(workInProgress ! = =null) {
// This is a sync render, so we should have finished the whole tree.
invariant(
false."Cannot commit an incomplete root. This error is likely caused by a " +
"bug in React. Please file an issue."
);
}
// Set this to null to indicate there's no in-progress render.
workInProgressRoot = null;
workInProgressRootRenderLanes = NoLanes;
return workInProgressRootExitStatus;
}
Copy the code
3.2.1 prepareFreshStack
Create workInProgress, workInProgress is very important as the primary key behind the virtual tree builds the entire tree for rendering
// Global variables
let workInProgress = null;
function prepareFreshStack(root, lanes) {
function createFiber(tag, pendingProps, key, mode) {
return new FiberNode(tag, pendingProps, key, mode);
}
function createWorkInProgress(current, pendingProps) {
let workInProgress = current.alternate;
if (workInProgress === null) {
// We use dual buffer pool technology
// Because we know we need at most two versions of a number
// We merge "other" unused resources so that we can freely reuse other nodes
// This lazy creation is to avoid allocating extra objects that are never updated
// This also allows us to reclaim additional memory if needed
workInProgress = createFiber(
current.tag,
pendingProps,
current.key,
current.mode
);
workInProgress.elementType = current.elementType;
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;
workInProgress.alternate = current;
current.alternate = workInProgress;
} else {
workInProgress.pendingProps = pendingProps;
// Data must be stored by type for Blocks
workInProgress.type = current.type;
// We already have a backup
/ / reset effectTag
workInProgress.effectTag = NoEffect;
// effect Lists no longer take effect
workInProgress.nextEffect = null;
workInProgress.firstEffect = null;
workInProgress.lastEffect = null;
if (enableProfilerTimer) {
workInProgress.actualDuration = 0;
workInProgress.actualStartTime = - 1;
}
}
workInProgress.childLanes = current.childLanes;
workInProgress.lanes = current.lanes;
workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.memoizedState = current.memoizedState;
workInProgress.updateQueue = current.updateQueue;
// Clone dependent objects and mutate them during rendering, this cannot be shared with current Fiber
const currentDependencies = current.dependencies_new;
workInProgress.dependencies_new =
currentDependencies === null
? null
: {
lanes: currentDependencies.lanes,
firstContext: currentDependencies.firstContext,
responders: currentDependencies.responders,
};
// These will be overwritten during the parent's reconciliation phase
workInProgress.sibling = current.sibling;
workInProgress.index = current.index;
workInProgress.ref = current.ref;
if (enableProfilerTimer) {
workInProgress.selfBaseDuration = current.selfBaseDuration;
workInProgress.treeBaseDuration = current.treeBaseDuration;
}
return workInProgress;
}
root.finishedWork = null;
root.finishedLanes = NoLanes;
const timeoutHandle = root.timeoutHandle;
if(timeoutHandle ! == noTimeout) {// The root previous suspended and scheduled a timeout to commit a fallback
// state. Now that we have additional work, cancel the timeout.
root.timeoutHandle = noTimeout;
// $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above
cancelTimeout(timeoutHandle);
}
if(workInProgress ! = =null) {
let interruptedWork = workInProgress.return;
while(interruptedWork ! = =null) {
unwindInterruptedWork(interruptedWork);
interruptedWork = interruptedWork.return;
}
}
workInProgressRoot = root;
workInProgress = createWorkInProgress(root.current, null);
workInProgressRootRenderLanes = subtreeRenderLanes = workInProgressRootIncludedLanes = lanes;
workInProgressRootExitStatus = RootIncomplete;
workInProgressRootFatalError = null;
workInProgressRootLatestProcessedEventTime = - 1;
workInProgressRootLatestSuspenseTimeout = - 1;
workInProgressRootCanSuspendUsingConfig = null;
workInProgressRootSkippedLanes = NoLanes;
workInProgressRootUpdatedLanes = NoLanes;
workInProgressRootPingedLanes = NoLanes;
if (enableSchedulerTracing) {
spawnedWorkDuringRender = null; }}Copy the code
3.2.2 workLoopSync
The performUnitOfWork method loops to beginWork to find the hander method that processes the corresponding component and creates the child nodes in a depth-first (first-order first) manner. If there are multiple child nodes under the same node, A sibling attribute is created for each node to point to the next sibling node and the completeUnitOfWork method is called when traversing to the deepest node of a branch (there are no children) to determine if there is Sibling (the next sibling) returns to performUnitOfWork if there is no sibling look for return (parent), If the parent node has sibling continue to return to performUnitOfWork to implement depth-first traversal to create the entire Fiber tree
function workLoopSync() {
function performUnitOfWork(unitOfWork) {
// Create a backup fiber
// Ideally, create a workInProgress without relying on Current Fiber
// Create an alternate property for workInProgress to point to current Fiber
const current = unitOfWork.alternate;
let next;
if(enableProfilerTimer && (unitOfWork.mode & ProfileMode) ! == NoMode) { startProfilerTimer(unitOfWork); next = beginWork(current, unitOfWork, subtreeRenderLanes); stopProfilerTimerIfRunningAndRecordDelta(unitOfWork,true);
} else {
// true
// workInProgress.alternate, workInProgress, SyncLane
next = beginWork(current, unitOfWork, subtreeRenderLanes);
}
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
// If no new work is assigned, complete the current work
completeUnitOfWork(unitOfWork);
} else {
// workInProgress is set to the next job
workInProgress = next;
}
ReactCurrentOwner.current = null;
}
function beginWork(current, workInProgress, renderLanes) {
const updateLanes = workInProgress.lanes;
if(current ! = =null) {
const oldProps = current.memoizedProps;
const newProps = workInProgress.pendingProps;
// If renderLanes does not contain lanes of the current workInProgress enter here
if(! includesSomeLane(renderLanes, updateLanes)) {// ...
} else {
// An update is scheduled for fiber, but he has no new props either
// legacy context. Set didReceiveUpdate to false if an update queue
// Or a change in the value generated by the context consumer will set didReceiveUpdate to
// true. Otherwise, the reorganization assumes that children do not change and exit
didReceiveUpdate = false; }}else {
didReceiveUpdate = false;
}
Clear the waiting update priority before entering the BEGIN phase.
workInProgress.lanes = NoLanes;
// Handle according to various types of tags
switch (workInProgress.tag) {
// ...
case IndeterminateComponent: {
return mountIndeterminateComponent(
current,
workInProgress,
workInProgress.type,
renderLanes
);
}
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,
renderLanes
);
}
case HostRoot:
return updateHostRoot(current, workInProgress, renderLanes);
case HostComponent:
return updateHostComponent(current, workInProgress, renderLanes);
case HostText:
return updateHostText(current, workInProgress);
// ...}}// It's already timed out, so it works without checking if we need output.
while(workInProgress ! = =null) { performUnitOfWork(workInProgress); }}Copy the code
3.2.2.1 processUpdateQueue
let workInProgressRootSkippedLanes = NoLanes; / / 0
function processUpdateQueue(workInProgress, props, instance, renderLanes) {
// Whether subset NoLane 0 is a subset of any number
function isSubsetOfLanes(set, subset) {
return (set & subset) === subset;
}
// Get state from update
function getStateFromUpdate(workInProgress, queue, update, prevState, nextProps, instance) {
switch (update.tag) {
// Reset the state
case ReplaceState: {
const payload = update.payload;
if (typeof payload === "function") {
const nextState = payload.call(instance, prevState, nextProps);
return nextState;
}
return payload;
}
case CaptureUpdate: {
workInProgress.effectTag =
(workInProgress.effectTag & ~ShouldCapture) | DidCapture;
}
// Update status entered here for the first time
case UpdateState: {
const payload = update.payload;
let partialState;
// if update is a method such as this.setstate ((state, props) => (newState)) calling the method returns the newState
if (typeof payload === "function") {
partialState = payload.call(instance, prevState, nextProps);
} else {
// Partial state objects
partialState = payload;
}
if (partialState === null || partialState === undefined) {
// If the state is empty, the last state is returned
return prevState;
}
// Merge the previous state with the newly obtained partial state shallow merge
return Object.assign({}, prevState, partialState);
}
// Force an update
case ForceUpdate: {
hasForceUpdate = true;
returnprevState; }}return prevState;
}
/ / global variable workInProgressRootSkippedLanes consolidated incoming lane
function markSkippedUpdateLanes(lane) {
workInProgressRootSkippedLanes = mergeLanes(
lane,
workInProgressRootSkippedLanes
);
}
// This is always non-empty on ClassComponent or HostRoot.
const queue = workInProgress.updateQueue;
hasForceUpdate = false;
let firstBaseUpdate = queue.firstBaseUpdate;
let lastBaseUpdate = queue.lastBaseUpdate;
// Check if there are any waiting updates. If so, move it to the base queue.
let pendingQueue = queue.shared.pending;
if(pendingQueue ! = =null) {
queue.shared.pending = null;
// The pending queue is circular. Break the pointer between the first and last to make it acyclic.
const lastPendingUpdate = pendingQueue;
const firstPendingUpdate = lastPendingUpdate.next;
lastPendingUpdate.next = null;
// Add pending updates to the base queue
if (lastBaseUpdate === null) {
firstBaseUpdate = firstPendingUpdate;
} else {
lastBaseUpdate.next = firstPendingUpdate;
}
lastBaseUpdate = lastPendingUpdate;
// If there is currently a queue and it is different from the underlying queue, then we need to move the updated content to this queue as well.
// Since the base queue is a single linked list with no cycles, we can attach to both lists and take advantage of structure sharing.
const current = workInProgress.alternate;
if(current ! = =null) {
// This is always non-empty on ClassComponent or HostRoot.
const currentQueue = current.updateQueue;
const currentLastBaseUpdate = currentQueue.lastBaseUpdate;
if(currentLastBaseUpdate ! == lastBaseUpdate) {if (currentLastBaseUpdate === null) {
currentQueue.firstBaseUpdate = firstPendingUpdate;
} else{ currentLastBaseUpdate.next = firstPendingUpdate; } currentQueue.lastBaseUpdate = lastPendingUpdate; }}}// These values may change as we process the queue.
if(firstBaseUpdate ! = =null) {
let newState = queue.baseState;
let newLanes = NoLanes;
let newBaseState = null;
let newFirstBaseUpdate = null;
let newLastBaseUpdate = null;
let update = firstBaseUpdate;
do {
const updateLane = update.lane;
const updateEventTime = update.eventTime;
if(! isSubsetOfLanes(renderLanes, updateLane)) {// Lack of priority. Skip this update. If this is the first one
// Skip the update, the previous update/state is the new base update/state.
const clone = {
eventTime: updateEventTime,
lane: updateLane,
suspenseConfig: update.suspenseConfig,
tag: update.tag,
payload: update.payload,
callback: update.callback,
next: null};if (newLastBaseUpdate === null) {
newFirstBaseUpdate = newLastBaseUpdate = clone;
newBaseState = newState;
} else {
newLastBaseUpdate = newLastBaseUpdate.next = clone;
}
// Update the remaining priorities in the queue.
newLanes = mergeLanes(newLanes, updateLane);
} else {
// This update does have sufficient priority.
if(newLastBaseUpdate ! = =null) {
const clone = {
eventTime: updateEventTime,
// This update will be committed, so we never want to cancel the commit
/ / it. Using NoLane is possible because 0 is a subset of all bitmasks, so
// This will never be skipped by the check above.
lane: NoLane,
suspenseConfig: update.suspenseConfig,
tag: update.tag,
payload: update.payload,
callback: update.callback,
next: null}; newLastBaseUpdate = newLastBaseUpdate.next = clone; }// Mark the event time of this update as relevant to the rendering process.
// TODO: Ideally, use the actual event time of this update instead of
// Its priority, which is a derived and irreversible value.
// To-do list: Skip this update if it has been committed but is not currently being performed
// We cannot detect the difference between committed and paused
// Update here.
// Process this update.
newState = getStateFromUpdate(
workInProgress,
queue,
update,
newState,
props,
instance
);
const callback = update.callback;
if(callback ! = =null) {
workInProgress.effectTag |= Callback;
const effects = queue.effects;
if (effects === null) {
queue.effects = [update];
} else {
effects.push(update);
}
}
}
update = update.next;
if (update === null) {
pendingQueue = queue.shared.pending;
if (pendingQueue === null) {
// Exit the while loop
break;
} else {
// When will you enter here
A update was scheduled from the Reducer internal
// Add new updates to the end of the list to be processed and continue processing.
const lastPendingUpdate = pendingQueue;
// Intentionally unsound. To be updated forms a circular linked list, but we
// Unpack them when moving to the base queue.
const firstPendingUpdate = lastPendingUpdate.next;
lastPendingUpdate.next = null;
update = firstPendingUpdate;
queue.lastBaseUpdate = lastPendingUpdate;
queue.shared.pending = null; }}}while (true);
if (newLastBaseUpdate === null) {
// true
newBaseState = newState;
}
queue.baseState = newBaseState;
queue.firstBaseUpdate = newFirstBaseUpdate; // null
queue.lastBaseUpdate = newLastBaseUpdate; // null
markSkippedUpdateLanes(newLanes); // workInProgressRootSkippedLanes = 0
workInProgress.lanes = newLanes; / / 0workInProgress.memoizedState = newState; }}Copy the code
3.2.2.1 updateHostRoot
function updateHostRoot(current, workInProgress, renderLanes) {
function cloneUpdateQueue(current, workInProgress) {
// Clone Update Quene from the current node, unless it is already cloned
const queue = workInProgress.updateQueue;
const currentQueue = current.updateQueue;
/ / shallow copy
if (queue === currentQueue) {
const clone = {
baseState: currentQueue.baseState,
firstBaseUpdate: currentQueue.firstBaseUpdate,
lastBaseUpdate: currentQueue.lastBaseUpdate,
shared: currentQueue.shared,
effects: currentQueue.effects, }; workInProgress.updateQueue = clone; }}/ / the context
pushHostRootContext(workInProgress);
const updateQueue = workInProgress.updateQueue;
const nextProps = workInProgress.pendingProps;
const prevState = workInProgress.memoizedState;
constprevChildren = prevState ! = =null ? prevState.element : null;
// create a copy of updateQueue
cloneUpdateQueue(current, workInProgress);
processUpdateQueue(workInProgress, nextProps, null, renderLanes);
const nextState = workInProgress.memoizedState;
const nextChildren = nextState.element;
if (nextChildren === prevChildren) {
// false
resetHydrationState();
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
const root = workInProgress.stateNode;
if (root.hydrate && enterHydrationState(workInProgress)) {
// There is no need to know
} else {
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
}
return workInProgress.child;
}
Copy the code
3.2.2.3 updateClassComponent
Handles the ClassComponent node, which contains the instantiation and invocation of the component’s lifecycle (note the lifecycle here the parent component’s lifecycle methods are called before the child component’s lifecycle methods)
const emptyRefsObject = new Component().refs;
function updateClassComponent(current, workInProgress, Component, nextProps, renderLanes) {
function isLegacyContextProvider(type) {
if (disableLegacyContext) {
// false
return false;
} else {
// true
const childContextTypes = type.childContextTypes; // undefined
returnchildContextTypes ! = =null&& childContextTypes ! = =undefined; }}function prepareToReadContext(workInProgress, renderLanes) {
currentlyRenderingFiber = workInProgress;
lastContextDependency = null;
lastContextWithAllBitsObserved = null;
const dependencies = workInProgress.dependencies_new; // null
if(dependencies ! = =null) {
// false
const firstContext = dependencies.firstContext;
if(firstContext ! = =null) {
if (includesSomeLane(dependencies.lanes, renderLanes)) {
// The context list has a pending update. Mark the fiber as completed.
markWorkInProgressReceivedUpdate();
}
// Reset the work-in-progress list
dependencies.firstContext = null; }}}function getUnmaskedContext(workInProgress, Component, didPushOwnContextIfProvider) {
if (disableLegacyContext) {
return emptyContextObject;
} else {
if (didPushOwnContextIfProvider && isLegacyContextProvider(Component)) {
// If the fiber is a context provider itself, when we read its context
// we may have already pushed its own child context on the stack. A context
// provider should not "see" its own child context. Therefore we read the
// previous (parent) context instead for a context provider.
return previousContext;
}
returncontextStackCursor.current; }}function setInstance(key, value) {
key._reactInternals = value;
}
function adoptClassInstance(workInProgress, instance) {
instance.updater = classComponentUpdater;
workInProgress.stateNode = instance;
// The instance needs to access Fiber to schedule updates
setInstance(instance, workInProgress);
if(__DEV__) { instance._reactInternalInstance = fakeInternalInstance; }}function constructClassInstance(workInProgress, ctor, props) {
let isLegacyContextConsumer = false;
let unmaskedContext = emptyContextObject;
let context = emptyContextObject;
const contextType = ctor.contextType; // undefined
if (typeof contextType === "object"&& contextType ! = =null) {
context = readContext(contextType);
} else if(! disableLegacyContext) {// true
// Do not understand
unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
constcontextTypes = ctor.contextTypes; isLegacyContextConsumer = contextTypes ! = =null&& contextTypes ! = =undefined; // false
// Old context consumer
context = isLegacyContextConsumer
? getMaskedContext(workInProgress, unmaskedContext)
: emptyContextObject;
}
// Instantiate the component
const instance = new ctor(props, context);
// Get the initial state of the instantiated component
conststate = (workInProgress.memoizedState = instance.state ! = =null&& instance.state ! = =undefined
? instance.state
: null); // { text: "Hello World" }
// Associate the instance with fiber
adoptClassInstance(workInProgress, instance);
// Do not understand
if (isLegacyContextConsumer) {
cacheContext(workInProgress, unmaskedContext, context);
}
return instance;
}
function mountClassInstance(workInProgress, ctor, newProps, renderLanes) {
function applyDerivedStateFromProps(workInProgress, ctor, getDerivedStateFromProps, nextProps) {
const prevState = workInProgress.memoizedState;
const partialState = getDerivedStateFromProps(nextProps, prevState);
// Merge the previous state with the state obtained by the getDerivedStateFromProps method
const memoizedState =
partialState === null || partialState === undefined
? prevState
: Object.assign({}, prevState, partialState);
workInProgress.memoizedState = memoizedState;
// When the update queue is empty, the derived state is left at base
if (workInProgress.lanes === NoLanes) {
// Queues are always non-null for classes
constupdateQueue = workInProgress.updateQueue; updateQueue.baseState = memoizedState; }}const instance = workInProgress.stateNode;
instance.props = newProps; / / {}
instance.state = workInProgress.memoizedState; // { text: "Hello World" }
instance.refs = emptyRefsObject; / / {}
// Initialize the update queue
initializeUpdateQueue(workInProgress);
const contextType = ctor.contextType;
if (typeof contextType === "object"&& contextType ! = =null) {
instance.context = readContext(contextType);
} else if (disableLegacyContext) {
instance.context = emptyContextObject; / / {}
} else {
const unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
instance.context = getMaskedContext(workInProgress, unmaskedContext);
}
processUpdateQueue(workInProgress, newProps, instance, renderLanes);
instance.state = workInProgress.memoizedState;
// Load Lifecycle one (getDerivedStateFromProps): getDerivedStateFromProps is an attribute of the constructor, so it must be static
const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
if (typeof getDerivedStateFromProps === "function") {
applyDerivedStateFromProps(
workInProgress,
ctor,
getDerivedStateFromProps,
newProps
);
instance.state = workInProgress.memoizedState;
}
// React life cycle polyfilled
// Unsafe life cycles should not be called, new apis should be used in the build
/ / load the lifecycle (UNSAFE_componentWillMount | | componentWillMount) : Note that this is not called if the getDerivedStateFromProps and getSnapshotBeforeUpdate lifecycleare used together
if (
typeofctor.getDerivedStateFromProps ! = ="function" &&
typeofinstance.getSnapshotBeforeUpdate ! = ="function" &&
(typeof instance.UNSAFE_componentWillMount === "function" ||
typeof instance.componentWillMount === "function")
) {
callComponentWillMount(workInProgress, instance);
// If there are other status updates during this lifecycle, let's
// Deal with them immediately
processUpdateQueue(workInProgress, newProps, instance, renderLanes);
instance.state = workInProgress.memoizedState;
}
// WorkinProgress. effectTag is set to include Update tag for componentDidMount life cycle
if (typeof instance.componentDidMount === "function") { workInProgress.effectTag |= Update; }}function markRef(current, workInProgress) {
const ref = workInProgress.ref;
if (
(current === null&& ref ! = =null) || (current ! = =null&& current.ref ! == ref) ) {// Schedule a Ref effectworkInProgress.effectTag |= Ref; }}function finishClassComponent(current, workInProgress, Component, shouldUpdate, hasContext, renderLanes) {
// The reference should be updated, even if shouldComponentUpdate returns false
markRef(current, workInProgress);
// Whether an error was caught
constdidCaptureError = (workInProgress.effectTag & DidCapture) ! == NoEffect;if(! shouldUpdate && ! didCaptureError) {// Context providers should defer to sCU for rendering
if (hasContext) {
invalidateContextProvider(workInProgress, Component, false);
}
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
const instance = workInProgress.stateNode;
// Rerender
ReactCurrentOwner.current = workInProgress;
let nextChildren;
if (
didCaptureError &&
typeofComponent.getDerivedStateFromError ! = ="function"
) {
// If we catch an error but do not define the getDerivedStateFromError method, unload all child components.
// componentDidCatch will schedule an update to re-render a fallback
// This is temporary until we all use the new API
nextChildren = null;
if(enableProfilerTimer) { stopProfilerTimerIfRunning(workInProgress); }}else {
if (__DEV__) {
setIsRendering(true);
nextChildren = instance.render();
if (
debugRenderPhaseSideEffectsForStrictMode &&
workInProgress.mode & StrictMode
) {
disableLogs();
try {
instance.render();
} finally {
reenableLogs();
}
}
setIsRendering(false);
} else {
// Returns from calling the render method of the component instancenextChildren = instance.render(); }}// React DevTools reads this flag.
workInProgress.effectTag |= PerformedWork;
if(current ! = =null && didCaptureError) {
// If we are recovering from an error
// Please adjust without taking any adjustment
// Existing subcomponents. Conceptually, normal child components and error display
// The subcomponents of the // are two different collections, so we should not
// Reuse normal child components even if their identities match
forceUnmountCurrentAndReconcile(
current,
workInProgress,
nextChildren,
renderLanes
);
} else {
// Call the reconcileChildren method to adjust the child components
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
}
// Use the values we just used for rendering to remember the state.
workInProgress.memoizedState = instance.state;
// The context may have changed, so we need to recalculate.
if (hasContext) {
invalidateContextProvider(workInProgress, Component, true);
}
return workInProgress.child;
}
// Push context providers early to prevent context stack mismatches.
// During the mount, we do not know about the subcontext because the instance does not exist.
// After rendering, we will invalidate the subcontext in finishClassComponent ().
let hasContext;
if (isLegacyContextProvider(Component)) {
hasContext = true;
pushLegacyContextProvider(workInProgress);
} else {
// true
hasContext = false;
}
prepareToReadContext(workInProgress, renderLanes);
const instance = workInProgress.stateNode;
let shouldUpdate;
if (instance === null) {
// true
if(current ! = =null) {
// false
// A class component without an instance only mounts if it suspended
// inside a non-concurrent tree, in an inconsistent state. We want to
// treat it like a new mount, even though an empty version of it already
// committed. Disconnect the alternate pointers.
current.alternate = null;
workInProgress.alternate = null;
// Since this is conceptually a new fiber, schedule a Placement effect
workInProgress.effectTag |= Placement;
}
// In the initial process, we may need to construct instances.
constructClassInstance(workInProgress, Component, nextProps);
mountClassInstance(workInProgress, Component, nextProps, renderLanes);
shouldUpdate = true;
} else if (current === null) {
// In a resume, we'll already have an instance we can reuse.
shouldUpdate = resumeMountClassInstance(
workInProgress,
Component,
nextProps,
renderLanes
);
} else {
shouldUpdate = updateClassInstance(
current,
workInProgress,
Component,
nextProps,
renderLanes
);
}
const nextUnitOfWork = finishClassComponent(
current,
workInProgress,
Component,
shouldUpdate,
hasContext,
renderLanes
);
if (__DEV__) {
const inst = workInProgress.stateNode;
if(shouldUpdate && inst.props ! == nextProps) {if(! didWarnAboutReassigningProps) {console.error(
"It looks like %s is reassigning its own `this.props` while rendering. " +
"This is not supported and can lead to confusing bugs.",
getComponentName(workInProgress.type) || "a component"
);
}
didWarnAboutReassigningProps = true; }}return nextUnitOfWork;
}
Copy the code
3.2.2.4 updateHostComponent
function updateHostComponent(current, workInProgress, renderLanes) {
pushHostContext(workInProgress);
if (current === null) {
// tryToClaimNextHydratableInstance(workInProgress);
}
const type = workInProgress.type;
const nextProps = workInProgress.pendingProps;
constprevProps = current ! = =null ? current.memoizedProps : null;
let nextChildren = nextProps.children;
const isDirectTextChild = shouldSetTextContent(type, nextProps);
if (isDirectTextChild) {
// We special case a direct text child of a host node. This is a common
// case. We won't handle it as a reified child. We will instead handle
// this in the host environment that also has access to this prop. That
// avoids allocating another HostText fiber and traversing it.
nextChildren = null;
} else if(prevProps ! = =null && shouldSetTextContent(type, prevProps)) {
// If we're switching from a direct text child to a normal child, or to
// empty, we need to schedule the text content to be reset.
workInProgress.effectTag |= ContentReset;
}
markRef(current, workInProgress);
if( (workInProgress.mode & ConcurrentMode) ! == NoMode && nextProps.hasOwnProperty("hidden")) {const wrappedChildren = {
? typeof: REACT_ELEMENT_TYPE,
type: REACT_LEGACY_HIDDEN_TYPE,
key: null.ref: null.props: {
children: nextChildren,
// Check the host config to see if the children are offscreen/hidden.
mode: shouldDeprioritizeSubtree(type, nextProps) ? "hidden" : "visible",},_owner: __DEV__ ? {} : null}; nextChildren = wrappedChildren; }// Adjust the child nodes of the current fiber node
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
Copy the code
4 reconcileChildren
function reconcileChildren(current, workInProgress, nextChildren, renderLanes) {
if (current === null) {
// If this is a brand new component that has not yet been rendered
// We do not update the subset by applying minimal side effects
// Instead, we add them all to the child before rendering them
// This means we can optimize the reconciliation process by not tracking side effects
workInProgress.child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderLanes
);
} else {
// If current Child and workInProgress are identical,
// This means that we have not started any work on these children
// Therefore, we use the clone algorithm to create a copy of the Current Child
// If we already have some of the work done, it is invalid at this point
// Let's throw it outworkInProgress.child = reconcileChildFibers( workInProgress, current.child, nextChildren, renderLanes ); }}Copy the code
4.1 reconcileChildFibers & mountChildFibers
ReconcileChildFibers reconcileChildFibers adjust (create, delete, and update) the Fiber child nodes to set the key attributes of Fiber including ref, Sibling, and return for reconcileChildFibers and mountChildFibers. The difference between the former reconcileChildFibers and mountChildFibers is that the former tracks side effects and the latter does not
const reconcileChildFibers = ChildReconciler(true);
const mountChildFibers = ChildReconciler(false);
// Adjust the children of the component
function ChildReconciler(shouldTrackSideEffects) {
function deleteChild(returnFiber, childToDelete) {
if(! shouldTrackSideEffects) {// Noop.
return;
}
// Deletions are added in reversed order so we add it to the front.
// At this point, the return fiber's effect list is empty except for
// deletions, so we can just append the deletion to the list. The remaining
// effects aren't added until the complete phase. Once we implement
// resuming, this may not be true.
const last = returnFiber.lastEffect;
if(last ! = =null) {
last.nextEffect = childToDelete;
returnFiber.lastEffect = childToDelete;
} else {
returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
}
childToDelete.nextEffect = null;
childToDelete.effectTag = Deletion;
}
function deleteRemainingChildren(returnFiber, currentFirstChild) {
if(! shouldTrackSideEffects) {// Noop.
return null;
}
// TODO: For the shouldClone case, this could be micro-optimized a bit by
// assuming that after the first child we've already added everything.
let childToDelete = currentFirstChild;
while(childToDelete ! = =null) {
deleteChild(returnFiber, childToDelete);
childToDelete = childToDelete.sibling;
}
return null;
}
function mapRemainingChildren(returnFiber, currentFirstChild) {
// Add the remaining children to a temporary map so that we can find them by
// keys quickly. Implicit (null) keys get added to this set with their index
// instead.
const existingChildren = new Map(a);let existingChild = currentFirstChild;
while(existingChild ! = =null) {
if(existingChild.key ! = =null) {
existingChildren.set(existingChild.key, existingChild);
} else {
existingChildren.set(existingChild.index, existingChild);
}
existingChild = existingChild.sibling;
}
return existingChildren;
}
function useFiber(fiber, pendingProps) {
// We currently set sibling to null and index to 0 here because it is easy
// to forget to do before returning it. E.g. for the single child case.
const clone = createWorkInProgress(fiber, pendingProps);
clone.index = 0;
clone.sibling = null;
return clone;
}
function placeChild(newFiber, lastPlacedIndex, newIndex) {
newFiber.index = newIndex;
if(! shouldTrackSideEffects) {// Noop.
return lastPlacedIndex;
}
const current = newFiber.alternate;
if(current ! = =null) {
const oldIndex = current.index;
if (oldIndex < lastPlacedIndex) {
// This is a move.
newFiber.effectTag = Placement;
return lastPlacedIndex;
} else {
// This item can stay in place.
returnoldIndex; }}else {
// This is an insertion.
newFiber.effectTag = Placement;
returnlastPlacedIndex; }}function placeSingleChild(newFiber) {
// This is simpler for the single child case. We only need to do a
// placement for inserting new children.
if (shouldTrackSideEffects && newFiber.alternate === null) {
newFiber.effectTag = Placement;
}
return newFiber;
}
function updateTextNode(returnFiber, current, textContent, lanes) {
if (current === null|| current.tag ! == HostText) {// Insert
const created = createFiberFromText(textContent, returnFiber.mode, lanes);
created.return = returnFiber;
return created;
} else {
// Update
const existing = useFiber(current, textContent);
existing.return = returnFiber;
returnexisting; }}function updateElement(returnFiber, current, element, lanes) {
if(current ! = =null) {
if (
current.elementType === element.type ||
// Keep this check inline so it only runs on the false path:
(__DEV__ ? isCompatibleFamilyForHotReloading(current, element) : false)) {// Move based on index
const existing = useFiber(current, element.props);
existing.ref = coerceRef(returnFiber, current, element);
existing.return = returnFiber;
if (__DEV__) {
existing._debugSource = element._source;
existing._debugOwner = element._owner;
}
return existing;
} else if (enableBlocksAPI && current.tag === Block) {
// The new Block might not be initialized yet. We need to initialize
// it in case initializing it turns out it would match.
let type = element.type;
if (type.?typeof === REACT_LAZY_TYPE) {
type = resolveLazyType(type);
}
if (
type.?typeof === REACT_BLOCK_TYPE &&
type === current.type._render
) {
// Same as above but also update the .type field.
const existing = useFiber(current, element.props);
existing.return = returnFiber;
existing.type = type;
if (__DEV__) {
existing._debugSource = element._source;
existing._debugOwner = element._owner;
}
returnexisting; }}}// Insert
const created = createFiberFromElement(element, returnFiber.mode, lanes);
created.ref = coerceRef(returnFiber, current, element);
created.return = returnFiber;
return created;
}
function updatePortal(returnFiber, current, portal, lanes) {
if (
current === null|| current.tag ! == HostPortal || current.stateNode.containerInfo ! == portal.containerInfo || current.stateNode.implementation ! == portal.implementation ) {// Insert
const created = createFiberFromPortal(portal, returnFiber.mode, lanes);
created.return = returnFiber;
return created;
} else {
// Update
const existing = useFiber(current, portal.children || []);
existing.return = returnFiber;
returnexisting; }}function updateFragment(returnFiber, current, fragment, lanes, key) {
if (current === null|| current.tag ! == Fragment) {// Insert
const created = createFiberFromFragment(
fragment,
returnFiber.mode,
lanes,
key
);
created.return = returnFiber;
return created;
} else {
// Update
const existing = useFiber(current, fragment);
existing.return = returnFiber;
returnexisting; }}function createChild(returnFiber, newChild, lanes) {
if (typeof newChild === "string" || typeof newChild === "number") {
// Text nodes don't have keys. If the previous node is implicitly keyed
// we can continue to replace it without aborting even if it is not a text
// node.
const created = createFiberFromText(
"" + newChild,
returnFiber.mode,
lanes
);
created.return = returnFiber;
return created;
}
if (typeof newChild === "object"&& newChild ! = =null) {
switch (newChild.?typeof) {
case REACT_ELEMENT_TYPE: {
const created = createFiberFromElement(
newChild,
returnFiber.mode,
lanes
);
created.ref = coerceRef(returnFiber, null, newChild);
created.return = returnFiber;
return created;
}
case REACT_PORTAL_TYPE: {
const created = createFiberFromPortal(
newChild,
returnFiber.mode,
lanes
);
created.return = returnFiber;
returncreated; }}if (isArray(newChild) || getIteratorFn(newChild)) {
const created = createFiberFromFragment(
newChild,
returnFiber.mode,
lanes,
null
);
created.return = returnFiber;
return created;
}
throwOnInvalidObjectType(returnFiber, newChild);
}
if (__DEV__) {
if (typeof newChild === "function") { warnOnFunctionType(returnFiber); }}return null;
}
function updateSlot(returnFiber, oldFiber, newChild, lanes) {
// Update the fiber if the keys match, otherwise return null.
constkey = oldFiber ! = =null ? oldFiber.key : null;
if (typeof newChild === "string" || typeof newChild === "number") {
// Text nodes don't have keys. If the previous node is implicitly keyed
// we can continue to replace it without aborting even if it is not a text
// node.
if(key ! = =null) {
return null;
}
return updateTextNode(returnFiber, oldFiber, "" + newChild, lanes);
}
if (typeof newChild === "object"&& newChild ! = =null) {
switch (newChild.?typeof) {
case REACT_ELEMENT_TYPE: {
if (newChild.key === key) {
if (newChild.type === REACT_FRAGMENT_TYPE) {
return updateFragment(
returnFiber,
oldFiber,
newChild.props.children,
lanes,
key
);
}
return updateElement(returnFiber, oldFiber, newChild, lanes);
} else {
return null; }}case REACT_PORTAL_TYPE: {
if (newChild.key === key) {
return updatePortal(returnFiber, oldFiber, newChild, lanes);
} else {
return null; }}}if (isArray(newChild) || getIteratorFn(newChild)) {
if(key ! = =null) {
return null;
}
return updateFragment(returnFiber, oldFiber, newChild, lanes, null);
}
throwOnInvalidObjectType(returnFiber, newChild);
}
if (__DEV__) {
if (typeof newChild === "function") { warnOnFunctionType(returnFiber); }}return null;
}
function updateFromMap(existingChildren, returnFiber, newIdx, newChild, lanes) {
if (typeof newChild === "string" || typeof newChild === "number") {
// Text nodes don't have keys, so we neither have to check the old nor
// new node for the key. If both are text nodes, they match.
const matchedFiber = existingChildren.get(newIdx) || null;
return updateTextNode(returnFiber, matchedFiber, "" + newChild, lanes);
}
if (typeof newChild === "object"&& newChild ! = =null) {
switch (newChild.?typeof) {
case REACT_ELEMENT_TYPE: {
const matchedFiber =
existingChildren.get(
newChild.key === null ? newIdx : newChild.key
) || null;
if (newChild.type === REACT_FRAGMENT_TYPE) {
return updateFragment(
returnFiber,
matchedFiber,
newChild.props.children,
lanes,
newChild.key
);
}
return updateElement(returnFiber, matchedFiber, newChild, lanes);
}
case REACT_PORTAL_TYPE: {
const matchedFiber =
existingChildren.get(
newChild.key === null ? newIdx : newChild.key
) || null;
returnupdatePortal(returnFiber, matchedFiber, newChild, lanes); }}if (isArray(newChild) || getIteratorFn(newChild)) {
const matchedFiber = existingChildren.get(newIdx) || null;
return updateFragment(returnFiber, matchedFiber, newChild, lanes, null);
}
throwOnInvalidObjectType(returnFiber, newChild);
}
if (__DEV__) {
if (typeof newChild === "function") { warnOnFunctionType(returnFiber); }}return null;
}
/** * Warns if there is a duplicate or missing key */
function warnOnInvalidKey(child, knownKeys, returnFiber) {
if (__DEV__) {
if (typeofchild ! = ="object" || child === null) {
return knownKeys;
}
switch (child.?typeof) {
case REACT_ELEMENT_TYPE:
case REACT_PORTAL_TYPE:
warnForMissingKey(child, returnFiber);
const key = child.key;
if (typeofkey ! = ="string") {
break;
}
if (knownKeys === null) {
knownKeys = new Set(a); knownKeys.add(key);break;
}
if(! knownKeys.has(key)) { knownKeys.add(key);break;
}
console.error(
"Encountered two children with the same key, `%s`. " +
"Keys should be unique so that components maintain their identity " +
"across updates. Non-unique keys may cause children to be " +
"duplicated and/or omitted — the behavior is unsupported and " +
"could change in a future version.",
key
);
break;
default:
break; }}return knownKeys;
}
function reconcileChildrenArray(returnFiber, currentFirstChild, newChildren, lanes) {
// This algorithm can't optimize by searching from both ends since we
// don't have backpointers on fibers. I'm trying to see how far we can get
// with that model. If it ends up not being worth the tradeoffs, we can
// add it later.
// Even with a two ended optimization, we'd want to optimize for the case
// where there are few changes and brute force the comparison instead of
// going for the Map. It'd like to explore hitting that path first in
// forward-only mode and only go for the Map once we notice that we need
// lots of look ahead. This doesn't handle reversal as well as two ended
// search but that's unusual. Besides, for the two ended optimization to
// work on Iterables, we'd need to copy the whole set.
// In this first iteration, we'll just live with hitting the bad case
// (adding everything to a Map) in for every insert/move.
// If you change this code, also update reconcileChildrenIterator() which
// uses the same algorithm.
if (__DEV__) {
// First, validate keys.
let knownKeys = null;
for (let i = 0; i < newChildren.length; i++) {
constchild = newChildren[i]; knownKeys = warnOnInvalidKey(child, knownKeys, returnFiber); }}let resultingFirstChild = null;
let previousNewFiber = null;
let oldFiber = currentFirstChild;
let lastPlacedIndex = 0;
let newIdx = 0;
let nextOldFiber = null;
for(; oldFiber ! = =null && newIdx < newChildren.length; newIdx++) {
if (oldFiber.index > newIdx) {
nextOldFiber = oldFiber;
oldFiber = null;
} else {
nextOldFiber = oldFiber.sibling;
}
const newFiber = updateSlot(
returnFiber,
oldFiber,
newChildren[newIdx],
lanes
);
if (newFiber === null) {
// TODO: This breaks on empty slots like null children. That's
// unfortunate because it triggers the slow path all the time. We need
// a better way to communicate whether this was a miss or null,
// boolean, undefined, etc.
if (oldFiber === null) {
oldFiber = nextOldFiber;
}
break;
}
if (shouldTrackSideEffects) {
if (oldFiber && newFiber.alternate === null) {
// We matched the slot, but we didn't reuse the existing fiber, so we
// need to delete the existing child.
deleteChild(returnFiber, oldFiber);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
// TODO: Move out of the loop. This only happens for the first run.
resultingFirstChild = newFiber;
} else {
// TODO: Defer siblings if we're not at the right index for this slot.
// I.e. if we had null values before, then we want to defer this
// for each null value. However, we also don't want to call updateSlot
// with the previous one.
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
oldFiber = nextOldFiber;
}
if (newIdx === newChildren.length) {
// We've reached the end of the new children. We can delete the rest.
deleteRemainingChildren(returnFiber, oldFiber);
return resultingFirstChild;
}
if (oldFiber === null) {
// If we don't have any more existing children we can choose a fast path
// since the rest will all be insertions.
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = createChild(returnFiber, newChildren[newIdx], lanes);
if (newFiber === null) {
continue;
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
// TODO: Move out of the loop. This only happens for the first run.
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
return resultingFirstChild;
}
// Add all children to a key map for quick lookups.
const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
// Keep scanning and use the map to restore deleted items as moves.
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = updateFromMap(
existingChildren,
returnFiber,
newIdx,
newChildren[newIdx],
lanes
);
if(newFiber ! = =null) {
if (shouldTrackSideEffects) {
if(newFiber.alternate ! = =null) {
// The new fiber is a work in progress, but if there exists a
// current, that means that we reused the fiber. We need to delete
// it from the child list so that we don't add it to the deletion
// list.
existingChildren.delete(
newFiber.key === null ? newIdx : newFiber.key
);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else{ previousNewFiber.sibling = newFiber; } previousNewFiber = newFiber; }}if (shouldTrackSideEffects) {
// Any existing children that weren't consumed above were deleted. We need
// to add them to the deletion list.
existingChildren.forEach((child) = > deleteChild(returnFiber, child));
}
return resultingFirstChild;
}
function reconcileChildrenIterator(returnFiber, currentFirstChild, newChildrenIterable, lanes) {
// This is the same implementation as reconcileChildrenArray(),
// but using the iterator instead.
const iteratorFn = getIteratorFn(newChildrenIterable);
invariant(
typeof iteratorFn === "function"."An object is not an iterable. This error is likely caused by a bug in " +
"React. Please file an issue."
);
if (__DEV__) {
// We don't support rendering Generators because it's a mutation.
// See https://github.com/facebook/react/issues/12995
if (
typeof Symbol= = ="function" &&
// $FlowFixMe Flow doesn't know about toStringTag
newChildrenIterable[Symbol.toStringTag] === "Generator"
) {
if(! didWarnAboutGenerators) {console.error(
"Using Generators as children is unsupported and will likely yield " +
"unexpected results because enumerating a generator mutates it. " +
"You may convert it to an array with `Array.from()` or the " +
"`[...spread]` operator before rendering. Keep in mind " +
"you might need to polyfill these features for older browsers."
);
}
didWarnAboutGenerators = true;
}
// Warn about using Maps as children
if (newChildrenIterable.entries === iteratorFn) {
if(! didWarnAboutMaps) {console.error(
"Using Maps as children is not supported. " +
"Use an array of keyed ReactElements instead."
);
}
didWarnAboutMaps = true;
}
// First, validate keys.
// We'll get a different iterator later for the main pass.
const newChildren = iteratorFn.call(newChildrenIterable);
if (newChildren) {
let knownKeys = null;
let step = newChildren.next();
for(; ! step.done; step = newChildren.next()) {constchild = step.value; knownKeys = warnOnInvalidKey(child, knownKeys, returnFiber); }}}constnewChildren = iteratorFn.call(newChildrenIterable); invariant(newChildren ! =null."An iterable object provided no iterator.");
let resultingFirstChild = null;
let previousNewFiber = null;
let oldFiber = currentFirstChild;
let lastPlacedIndex = 0;
let newIdx = 0;
let nextOldFiber = null;
let step = newChildren.next();
for(; oldFiber ! = =null && !step.done;
newIdx++, step = newChildren.next()
) {
if (oldFiber.index > newIdx) {
nextOldFiber = oldFiber;
oldFiber = null;
} else {
nextOldFiber = oldFiber.sibling;
}
const newFiber = updateSlot(returnFiber, oldFiber, step.value, lanes);
if (newFiber === null) {
// TODO: This breaks on empty slots like null children. That's
// unfortunate because it triggers the slow path all the time. We need
// a better way to communicate whether this was a miss or null,
// boolean, undefined, etc.
if (oldFiber === null) {
oldFiber = nextOldFiber;
}
break;
}
if (shouldTrackSideEffects) {
if (oldFiber && newFiber.alternate === null) {
// We matched the slot, but we didn't reuse the existing fiber, so we
// need to delete the existing child.
deleteChild(returnFiber, oldFiber);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
// TODO: Move out of the loop. This only happens for the first run.
resultingFirstChild = newFiber;
} else {
// TODO: Defer siblings if we're not at the right index for this slot.
// I.e. if we had null values before, then we want to defer this
// for each null value. However, we also don't want to call updateSlot
// with the previous one.
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
oldFiber = nextOldFiber;
}
if (step.done) {
// We've reached the end of the new children. We can delete the rest.
deleteRemainingChildren(returnFiber, oldFiber);
return resultingFirstChild;
}
if (oldFiber === null) {
// If we don't have any more existing children we can choose a fast path
// since the rest will all be insertions.
for(; ! step.done; newIdx++, step = newChildren.next()) {const newFiber = createChild(returnFiber, step.value, lanes);
if (newFiber === null) {
continue;
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
// TODO: Move out of the loop. This only happens for the first run.
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
return resultingFirstChild;
}
// Add all children to a key map for quick lookups.
const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
// Keep scanning and use the map to restore deleted items as moves.
for(; ! step.done; newIdx++, step = newChildren.next()) {const newFiber = updateFromMap(
existingChildren,
returnFiber,
newIdx,
step.value,
lanes
);
if(newFiber ! = =null) {
if (shouldTrackSideEffects) {
if(newFiber.alternate ! = =null) {
// The new fiber is a work in progress, but if there exists a
// current, that means that we reused the fiber. We need to delete
// it from the child list so that we don't add it to the deletion
// list.
existingChildren.delete(
newFiber.key === null ? newIdx : newFiber.key
);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else{ previousNewFiber.sibling = newFiber; } previousNewFiber = newFiber; }}if (shouldTrackSideEffects) {
// Any existing children that weren't consumed above were deleted. We need
// to add them to the deletion list.
existingChildren.forEach((child) = > deleteChild(returnFiber, child));
}
return resultingFirstChild;
}
function reconcileSingleTextNode(returnFiber, currentFirstChild, textContent, lanes) {
// There's no need to check for keys on text nodes since we don't have a
// way to define them.
if(currentFirstChild ! = =null && currentFirstChild.tag === HostText) {
// We already have an existing node so let's just update it and delete
// the rest.
deleteRemainingChildren(returnFiber, currentFirstChild.sibling);
const existing = useFiber(currentFirstChild, textContent);
existing.return = returnFiber;
return existing;
}
// The existing first child is not a text node so we need to create one
// and delete the existing ones.
deleteRemainingChildren(returnFiber, currentFirstChild);
const created = createFiberFromText(textContent, returnFiber.mode, lanes);
created.return = returnFiber;
return created;
}
function reconcileSingleElement(returnFiber, currentFirstChild, element, lanes) {
const key = element.key;
let child = currentFirstChild;
while(child ! = =null) {
// TODO: If key === null and child.key === null, then this only applies to
// the first item in the list.
if (child.key === key) {
switch (child.tag) {
case Fragment: {
if (element.type === REACT_FRAGMENT_TYPE) {
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, element.props.children);
existing.return = returnFiber;
if (__DEV__) {
existing._debugSource = element._source;
existing._debugOwner = element._owner;
}
return existing;
}
break;
}
case Block:
if (enableBlocksAPI) {
let type = element.type;
if (type.?typeof === REACT_LAZY_TYPE) {
type = resolveLazyType(type);
}
if (type.?typeof === REACT_BLOCK_TYPE) {
// The new Block might not be initialized yet. We need to initialize
// it in case initializing it turns out it would match.
if (type._render === child.type._render) {
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, element.props);
existing.type = type;
existing.return = returnFiber;
if (__DEV__) {
existing._debugSource = element._source;
existing._debugOwner = element._owner;
}
returnexisting; }}}// We intentionally fallthrough here if enableBlocksAPI is not on.
// eslint-disable-next-lined no-fallthrough
default: {
if (
child.elementType === element.type ||
// Keep this check inline so it only runs on the false path:
(__DEV__
? isCompatibleFamilyForHotReloading(child, element)
: false)
) {
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, element.props);
existing.ref = coerceRef(returnFiber, child, element);
existing.return = returnFiber;
if (__DEV__) {
existing._debugSource = element._source;
existing._debugOwner = element._owner;
}
return existing;
}
break; }}// Didn't match.
deleteRemainingChildren(returnFiber, child);
break;
} else {
deleteChild(returnFiber, child);
}
child = child.sibling;
}
if (element.type === REACT_FRAGMENT_TYPE) {
const created = createFiberFromFragment(
element.props.children,
returnFiber.mode,
lanes,
element.key
);
created.return = returnFiber;
return created;
} else {
const created = createFiberFromElement(element, returnFiber.mode, lanes);
created.ref = coerceRef(returnFiber, currentFirstChild, element);
created.return = returnFiber;
returncreated; }}function reconcileSinglePortal(returnFiber, currentFirstChild, portal, lanes) {
const key = portal.key;
let child = currentFirstChild;
while(child ! = =null) {
// TODO: If key === null and child.key === null, then this only applies to
// the first item in the list.
if (child.key === key) {
if (
child.tag === HostPortal &&
child.stateNode.containerInfo === portal.containerInfo &&
child.stateNode.implementation === portal.implementation
) {
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, portal.children || []);
existing.return = returnFiber;
return existing;
} else {
deleteRemainingChildren(returnFiber, child);
break; }}else {
deleteChild(returnFiber, child);
}
child = child.sibling;
}
const created = createFiberFromPortal(portal, returnFiber.mode, lanes);
created.return = returnFiber;
return created;
}
// 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, currentFirstChild, newChild, lanes) {
// 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 &&
newChild.type === REACT_FRAGMENT_TYPE &&
newChild.key === null;
if (isUnkeyedTopLevelFragment) {
newChild = newChild.props.children;
}
// Handle object types
const isObject = typeof newChild === "object"&& newChild ! = =null;
if (isObject) {
switch (newChild.?typeof) {
case REACT_ELEMENT_TYPE:
return placeSingleChild(
reconcileSingleElement(
returnFiber,
currentFirstChild,
newChild,
lanes
)
);
case REACT_PORTAL_TYPE:
returnplaceSingleChild( reconcileSinglePortal( returnFiber, currentFirstChild, newChild, lanes ) ); }}if (typeof newChild === "string" || typeof newChild === "number") {
return placeSingleChild(
reconcileSingleTextNode(
returnFiber,
currentFirstChild,
"" + newChild,
lanes
)
);
}
if (isArray(newChild)) {
return reconcileChildrenArray(
returnFiber,
currentFirstChild,
newChild,
lanes
);
}
if (getIteratorFn(newChild)) {
return reconcileChildrenIterator(
returnFiber,
currentFirstChild,
newChild,
lanes
);
}
if (isObject) {
throwOnInvalidObjectType(returnFiber, newChild);
}
if (__DEV__) {
if (typeof newChild === "function") { warnOnFunctionType(returnFiber); }}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.
switch (returnFiber.tag) {
case ClassComponent: {
if (__DEV__) {
const instance = returnFiber.stateNode;
if (instance.render._isMockFunction) {
// We allow auto-mocks to proceed as if they're returning null.
break; }}}// 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.
return deleteRemainingChildren(returnFiber, currentFirstChild);
}
return reconcileChildFibers;
}
Copy the code
5 completeUnitOfWork
Function: (1) Assist workLoopSync depth-first traversal to generate Fiber tree, principle: When workLoopSync traverses to the node with the deepest branch, look up the parent node and determine whether the parent node has sibling nodes. If so, set workInProgress to this node. To find the next node, determine the effectTag and create a list of side effects (from the child node up to the parent node) and call the completeWork method
Question ① Why to create side effects linked list answer: When project complex Fiber tree has a lot of nodes, if by iterating through each node to run time complexity will rise if the current node side effects, so in the build tree by judging each node effectTag will side effects relate to generate a list can effectively reduce the time complexity, promote efficiency of programs.
Question ② why the side effects of the child node rank before the side effects of the parent node: answer: ComponentDidMount is called only when the child component is finished rendering. So the componentDidMount life cycle of the child component must run before the parent component, so the side effects list sorting is implemented in accordance with the function, as is the REF method. The parent component should ensure that the child’s REF has been handled when fetching an instance from the child’s REF.
function completeUnitOfWork(unitOfWork) {
// Try to complete the current unit of work and move to the next sibling node
// If there are no more siblings, return to the parent node
let completedWork = unitOfWork;
do {
// The current node is refreshed. The state of the fiber is backed up.
Ideally nothing should come to it, is relying on it here means we don't
// Additional fields need to be added to workInProgress.
const current = completedWork.alternate;
const returnFiber = completedWork.return;
// Check to see if work is complete or if anything is thrown.
if ((completedWork.effectTag & Incomplete) === NoEffect) {
let next;
if (
!enableProfilerTimer ||
(completedWork.mode & ProfileMode) === NoMode
) {
// true
next = completeWork(current, completedWork, subtreeRenderLanes);
} else {
next = completeWork(current, completedWork, subtreeRenderLanes);
// Assuming we have no errors, please update the render time.
}
resetChildLanes(completedWork);
if(next ! = =null) {
// A new work is generated when the fiber node is completed
workInProgress = next;
return;
}
if( returnFiber ! = =null &&
// Do not add effects to the parent node if the generic node is not complete
(returnFiber.effectTag & Incomplete) === NoEffect
) {
// Add the current node's side effects to the parent node
// Recursively replace the order of child and parent side effects
// To achieve firstEffect as the deepest node with side effects
// nextEffect points to the parent node
if (returnFiber.firstEffect === null) {
returnFiber.firstEffect = completedWork.firstEffect;
}
if(completedWork.lastEffect ! = =null) {
if(returnFiber.lastEffect ! = =null) {
returnFiber.lastEffect.nextEffect = completedWork.firstEffect;
}
returnFiber.lastEffect = completedWork.lastEffect;
}
const effectTag = completedWork.effectTag;
// If the effectTag is greater than 1 (add ref, ClassComponent, etc.)
// If the parent does not have a side effect list, create a side effect list and add it to the parent (child side effects bubble up)
// If the parent node has a side effect list, use nextEffect to point to itself and reset lastEffect to itself
if (effectTag > PerformedWork) {
if(returnFiber.lastEffect ! = =null) {
returnFiber.lastEffect.nextEffect = completedWork;
} else{ returnFiber.firstEffect = completedWork; } returnFiber.lastEffect = completedWork; }}}else {
// This fiber did not complete because something threw. Pop values off
// the stack without entering the complete phase. If this is a boundary,
// capture values if possible.
const next = unwindWork(completedWork, subtreeRenderLanes);
// Because this fiber did not complete, don't reset its expiration time.
if( enableProfilerTimer && (completedWork.mode & ProfileMode) ! == NoMode ) {// Record the render duration for the fiber that errored.
stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
// Include the time spent working on failed children before continuing.
let actualDuration = completedWork.actualDuration;
let child = completedWork.child;
while(child ! = =null) {
actualDuration += child.actualDuration;
child = child.sibling;
}
completedWork.actualDuration = actualDuration;
}
if(next ! = =null) {
// If completing this work spawned new work, do that next. We'll come
// back here again.
// Since we're restarting, remove anything that is not a host effect
// from the effect tag.
next.effectTag &= HostEffectMask;
workInProgress = next;
return;
}
if(returnFiber ! = =null) {
// Mark the parent fiber as incomplete and clear its effect list.
returnFiber.firstEffect = returnFiber.lastEffect = null; returnFiber.effectTag |= Incomplete; }}const siblingFiber = completedWork.sibling;
if(siblingFiber ! = =null) {
// If the parent's sibling has more work to do, set it to next
workInProgress = siblingFiber;
return;
}
// Otherwise, return the parent node
completedWork = returnFiber;
workInProgress = completedWork;
} while(completedWork ! = =null);
// Complete the entire tree
if(workInProgressRootExitStatus === RootIncomplete) { workInProgressRootExitStatus = RootCompleted; }}Copy the code
5.1 completeWork
Function: According to the type of the current node, the function of creating and updating the real DOM (DOM is stored in memory after creation and not mounted to the container) includes the diff research of the virtual tree
function completeWork(current, workInProgress, renderLanes) {
function appendAllChildren(parent, workInProgress, needsVisibilityToggle, isHidden) {
// We only have the top Fiber that was created but we need recurse down its
// children to find all the terminal nodes.
let node = workInProgress.child;
while(node ! = =null) {
if (node.tag === HostComponent || node.tag === HostText) {
appendInitialChild(parent, node.stateNode);
} else if (enableFundamentalAPI && node.tag === FundamentalComponent) {
appendInitialChild(parent, node.stateNode.instance);
} else if (node.tag === HostPortal) {
// If we have a portal child, then we don't want to traverse
// down its children. Instead, we'll get insertions from each child in
// the portal directly.
} else if(node.child ! = =null) {
node.child.return = node;
node = node.child;
continue;
}
if (node === workInProgress) {
return;
}
while (node.sibling === null) {
if (node.return === null || node.return === workInProgress) {
return; } node = node.return; } node.sibling.return = node.return; node = node.sibling; }}function appendInitialChild(parentInstance, child) {
parentInstance.appendChild(child);
}
function updateHostContainer(workInProgress) {
// Noop
}
function updateHostComponent(current, workInProgress, type, newProps, rootContainerInstance) {
// If we have an alternate, that means this is an update and we need to
// schedule a side-effect to do the updates.
const oldProps = current.memoizedProps;
if (oldProps === newProps) {
// In mutation mode, this is sufficient for a bailout because
// we won't touch this node even if children changed.
return;
}
// If we get updated because one of our children updated, we don't
// have newProps so we'll have to reuse them.
// TODO: Split the update API as separate for the props vs. children.
// Even better would be if children weren't special cased at all tho.
const instance = workInProgress.stateNode;
const currentHostContext = getHostContext();
// TODO: Experiencing an error where oldProps is null. Suggests a host
// component is hitting the resume path. Figure out why. Possibly
// related to `hidden`.
const updatePayload = prepareUpdate(
instance,
type,
oldProps,
newProps,
rootContainerInstance,
currentHostContext
);
// TODO: Type this specific to this type of component.
workInProgress.updateQueue = updatePayload;
// If the update payload indicates that there is a change or if there
// is a new ref we mark this as an update. All the work is done in commitWork.
if(updatePayload) { markUpdate(workInProgress); }}function updateHostText(current, workInProgress, oldText, newText) {
// If the text differs, mark it as an update. All the work in done in commitWork.
if (oldText !== newText) {
markUpdate(workInProgress);
}
}
function finalizeInitialChildren(domElement, type, props, rootContainerInstance, hostContext) {
setInitialProperties(domElement, type, props, rootContainerInstance);
return shouldAutoFocusHostComponent(type, props);
}
function createInstance(type, props, rootContainerInstance, hostContext, internalInstanceHandle) {
let parentNamespace;
if (__DEV__) {
// TODO: take namespace into account when validating.
const hostContextDev = hostContext;
validateDOMNesting(type, null, hostContextDev.ancestorInfo);
if (
typeof props.children === "string" ||
typeof props.children === "number"
) {
const string = "" + props.children;
const ownAncestorInfo = updatedAncestorInfo(
hostContextDev.ancestorInfo,
type
);
validateDOMNesting(null, string, ownAncestorInfo);
}
parentNamespace = hostContextDev.namespace;
} else {
parentNamespace = hostContext;
}
const domElement = createRealElement(
// origin function name: createElement
type,
props,
rootContainerInstance,
parentNamespace
);
// Set a property in DOM to access the current Fiber backup
precacheFiberNode(internalInstanceHandle, domElement);
// Set a property in DOM to access the current fiber props
updateFiberProps(domElement, props);
return domElement;
}
function createRealElement(type, props, rootContainerElement, parentNamespace) {
let isCustomComponentTag;
// We create tags in the namespace of their parent container, except HTML
// tags get no namespace.
const ownerDocument = getOwnerDocumentFromRootContainer(
rootContainerElement
);
let domElement;
let namespaceURI = parentNamespace;
if (namespaceURI === HTML_NAMESPACE) {
namespaceURI = getIntrinsicNamespace(type);
}
if (namespaceURI === HTML_NAMESPACE) {
const lowerCaseType = type.toLowerCase();
if (__DEV__) {
isCustomComponentTag = isCustomComponent(type, props);
// Should this check be gated by parent namespace? Not sure we want to
// allow <SVG> or <mATH>.
if(! isCustomComponentTag && type ! == lowerCaseType) {console.error(
"<%s /> is using incorrect casing. " +
"Use PascalCase for React components, " +
"or lowercase for HTML elements.", type ); }}if (lowerCaseType === "script") {
// Create the script via .innerHTML so its "parser-inserted" flag is
// set to true and it does not execute
const div = ownerDocument.createElement("div");
if (__DEV__) {
if(enableTrustedTypesIntegration && ! didWarnScriptTags) {console.error(
"Encountered a script tag while rendering React component. " +
"Scripts inside React components are never executed when rendering " +
"on the client. Consider using template tag instead " +
"(https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template)."
);
didWarnScriptTags = true;
}
}
div.innerHTML = "<script><" + "/script>"; // eslint-disable-line
// This is guaranteed to yield a script element.
const firstChild = div.firstChild;
domElement = div.removeChild(firstChild);
} else if (typeof props.is === "string") {
// $FlowIssue `createElement` should be updated for Web Components
domElement = ownerDocument.createElement(type, { is: props.is });
} else {
// Separate else branch instead of using `props.is || undefined` above because of a Firefox bug.
// See discussion in https://github.com/facebook/react/pull/6896
// and discussion in https://bugzilla.mozilla.org/show_bug.cgi?id=1276240
domElement = ownerDocument.createElement(type);
// Normally attributes are assigned in `setInitialDOMProperties`, however the `multiple` and `size`
// attributes on `select`s needs to be added before `option`s are inserted.
// This prevents:
// - a bug where the `select` does not scroll to the correct option because singular
// `select` elements automatically pick the first item #13222
// - a bug where the `select` set the first item as selected despite the `size` attribute #14239
// See https://github.com/facebook/react/issues/13222
// and https://github.com/facebook/react/issues/14239
if (type === "select") {
const node = domElement;
if (props.multiple) {
node.multiple = true;
} else if (props.size) {
// Setting a size greater than 1 causes a select to behave like `multiple=true`, where
// it is possible that no option is selected.
//
// This is only necessary when a select in "single selection mode".node.size = props.size; }}}}else {
domElement = ownerDocument.createElementNS(namespaceURI, type);
}
if (__DEV__) {
if (namespaceURI === HTML_NAMESPACE) {
if (
!isCustomComponentTag &&
Object.prototype.toString.call(domElement) ===
"[object HTMLUnknownElement]"&&!Object.prototype.hasOwnProperty.call(warnedUnknownTags, type)
) {
warnedUnknownTags[type] = true;
console.error(
"The tag <%s> is unrecognized in this browser. " +
"If you meant to render a React component, start its name with " +
"an uppercase letter.", type ); }}}return domElement;
}
function setInitialProperties(domElement, tag, rawProps, rootContainerElement) {
const isCustomComponentTag = isCustomComponent(tag, rawProps);
if (__DEV__) {
validatePropertiesInDevelopment(tag, rawProps);
}
// TODO: Make sure that we check isMounted before firing any of these events.
let props;
switch (tag) {
case "iframe":
case "object":
case "embed":
if(! enableModernEventSystem) { legacyTrapBubbledEvent(TOP_LOAD, domElement); } props = rawProps;break;
case "video":
case "audio":
if(! enableModernEventSystem) {// Create listener for each media event
for (let i = 0; i < mediaEventTypes.length; i++) {
legacyTrapBubbledEvent(mediaEventTypes[i], domElement);
}
}
props = rawProps;
break;
case "source":
if(! enableModernEventSystem) { legacyTrapBubbledEvent(TOP_ERROR, domElement); } props = rawProps;break;
case "img":
case "image":
case "link":
if(! enableModernEventSystem) { legacyTrapBubbledEvent(TOP_ERROR, domElement); legacyTrapBubbledEvent(TOP_LOAD, domElement); } props = rawProps;break;
case "form":
if(! enableModernEventSystem) { legacyTrapBubbledEvent(TOP_RESET, domElement); legacyTrapBubbledEvent(TOP_SUBMIT, domElement); } props = rawProps;break;
case "details":
if(! enableModernEventSystem) { legacyTrapBubbledEvent(TOP_TOGGLE, domElement); } props = rawProps;break;
case "input":
ReactDOMInputInitWrapperState(domElement, rawProps);
props = ReactDOMInputGetHostProps(domElement, rawProps);
if(! enableModernEventSystem) { legacyTrapBubbledEvent(TOP_INVALID, domElement); }// For controlled components we always need to ensure we're listening
// to onChange. Even if there is no listener.
ensureListeningTo(rootContainerElement, "onChange");
break;
case "option":
ReactDOMOptionValidateProps(domElement, rawProps);
props = ReactDOMOptionGetHostProps(domElement, rawProps);
break;
case "select":
ReactDOMSelectInitWrapperState(domElement, rawProps);
props = ReactDOMSelectGetHostProps(domElement, rawProps);
if(! enableModernEventSystem) { legacyTrapBubbledEvent(TOP_INVALID, domElement); }// For controlled components we always need to ensure we're listening
// to onChange. Even if there is no listener.
ensureListeningTo(rootContainerElement, "onChange");
break;
case "textarea":
ReactDOMTextareaInitWrapperState(domElement, rawProps);
props = ReactDOMTextareaGetHostProps(domElement, rawProps);
if(! enableModernEventSystem) { legacyTrapBubbledEvent(TOP_INVALID, domElement); }// For controlled components we always need to ensure we're listening
// to onChange. Even if there is no listener.
ensureListeningTo(rootContainerElement, "onChange");
break;
default:
props = rawProps;
}
assertValidProps(tag, props);
setInitialDOMProperties(
tag,
domElement,
rootContainerElement,
props,
isCustomComponentTag
);
switch (tag) {
case "input":
// TODO: Make sure we check if this is still unmounted or do any clean
// up necessary since we never stop tracking anymore.
track(domElement);
ReactDOMInputPostMountWrapper(domElement, rawProps, false);
break;
case "textarea":
// TODO: Make sure we check if this is still unmounted or do any clean
// up necessary since we never stop tracking anymore.
track(domElement);
ReactDOMTextareaPostMountWrapper(domElement, rawProps);
break;
case "option":
ReactDOMOptionPostMountWrapper(domElement, rawProps);
break;
case "select":
ReactDOMSelectPostMountWrapper(domElement, rawProps);
break;
default:
if (typeof props.onClick === "function") {
// TODO: This cast may not be sound for SVG, MathML or custom elements.
trapClickOnNonInteractiveElement(domElement);
}
break; }}function setValueForProperty(node, name, value, isCustomComponentTag) {
const properties = {};
function getPropertyInfo(name) {
return properties.hasOwnProperty(name) ? properties[name] : null;
}
function shouldIgnoreAttribute(name, propertyInfo, isCustomComponentTag) {
if(propertyInfo ! = =null) {
return propertyInfo.type === RESERVED;
}
if (isCustomComponentTag) {
return false;
}
if (
name.length > 2 &&
(name[0= = ="o" || name[0= = ="O") &&
(name[1= = ="n" || name[1= = ="N")) {return true;
}
return false;
}
function shouldRemoveAttributeWithWarning(name, value, propertyInfo, isCustomComponentTag) {
if(propertyInfo ! = =null && propertyInfo.type === RESERVED) {
return false;
}
switch (typeof value) {
case "function":
// $FlowIssue symbol is perfectly valid here
case "symbol": // eslint-disable-line
return true;
case "boolean": {
if (isCustomComponentTag) {
return false;
}
if(propertyInfo ! = =null) {
return! propertyInfo.acceptsBooleans; }else {
const prefix = name.toLowerCase().slice(0.5);
returnprefix ! = ="data-"&& prefix ! = ="aria-"; }}default:
return false; }}function shouldRemoveAttribute(name, value, propertyInfo, isCustomComponentTag) {
const RESERVED = 0;
const STRING = 1;
const BOOLEANISH_STRING = 2;
const BOOLEAN = 3;
const OVERLOADED_BOOLEAN = 4;
const NUMERIC = 5;
const POSITIVE_NUMERIC = 6;
if (value === null || typeof value === "undefined") {
return true;
}
if (
shouldRemoveAttributeWithWarning(
name,
value,
propertyInfo,
isCustomComponentTag
)
) {
return true;
}
if (isCustomComponentTag) {
return false;
}
if(propertyInfo ! = =null) {
if (enableFilterEmptyStringAttributesDOM) {
if (propertyInfo.removeEmptyString && value === "") {
return true; }}switch (propertyInfo.type) {
case BOOLEAN:
return! value;case OVERLOADED_BOOLEAN:
return value === false;
case NUMERIC:
return isNaN(value);
case POSITIVE_NUMERIC:
return isNaN(value) || value < 1; }}return false;
}
const hasOwnProperty = Object.prototype.hasOwnProperty;
const illegalAttributeNameCache = {};
const validatedAttributeNameCache = {};
const ATTRIBUTE_NAME_START_CHAR =
":A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C0 0-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD";
const ATTRIBUTE_NAME_CHAR =
ATTRIBUTE_NAME_START_CHAR +
"\\-.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040";
const VALID_ATTRIBUTE_NAME_REGEX = new RegExp(
"^ [" + ATTRIBUTE_NAME_START_CHAR + "] [" + ATTRIBUTE_NAME_CHAR + "] * $"
);
function isAttributeNameSafe(attributeName) {
if (hasOwnProperty.call(validatedAttributeNameCache, attributeName)) {
return true;
}
if (hasOwnProperty.call(illegalAttributeNameCache, attributeName)) {
return false;
}
if (VALID_ATTRIBUTE_NAME_REGEX.test(attributeName)) {
validatedAttributeNameCache[attributeName] = true;
return true;
}
illegalAttributeNameCache[attributeName] = true;
return false;
}
const propertyInfo = getPropertyInfo(name);
if (shouldIgnoreAttribute(name, propertyInfo, isCustomComponentTag)) {
return;
}
if (
shouldRemoveAttribute(name, value, propertyInfo, isCustomComponentTag)
) {
value = null;
}
// If the prop isn't in the special list, treat it as a simple attribute.
if (isCustomComponentTag || propertyInfo === null) {
if (isAttributeNameSafe(name)) {
const attributeName = name;
if (value === null) {
node.removeAttribute(attributeName);
} else {
node.setAttribute(
attributeName,
enableTrustedTypesIntegration ? value : ""+ value ); }}return;
}
const { mustUseProperty } = propertyInfo;
if (mustUseProperty) {
const { propertyName } = propertyInfo;
if (value === null) {
const { type } = propertyInfo;
node[propertyName] = type === BOOLEAN ? false : "";
} else {
// Contrary to `setAttribute`, object properties are properly
// `toString`ed by IE8/9.
node[propertyName] = value;
}
return;
}
// The rest are treated as attributes with special cases.
const { attributeName, attributeNamespace } = propertyInfo;
if (value === null) {
node.removeAttribute(attributeName);
} else {
const { type } = propertyInfo;
let attributeValue;
if (type === BOOLEAN || (type === OVERLOADED_BOOLEAN && value === true)) {
// If attribute type is boolean, we know for sure it won't be an execution sink
// and we won't require Trusted Type here.
attributeValue = "";
} else {
// `setAttribute` with objects becomes only `[object]` in IE8/9,
// ('' + value) makes it output the correct toString()-value.
if (enableTrustedTypesIntegration) {
attributeValue = value;
} else {
attributeValue = "" + value;
}
if(propertyInfo.sanitizeURL) { sanitizeURL(attributeValue.toString()); }}if (attributeNamespace) {
node.setAttributeNS(attributeNamespace, attributeName, attributeValue);
} else{ node.setAttribute(attributeName, attributeValue); }}}function setInitialDOMProperties(tag, domElement, rootContainerElement, nextProps, isCustomComponentTag) {
const DANGEROUSLY_SET_INNER_HTML = "dangerouslySetInnerHTML";
const SUPPRESS_CONTENT_EDITABLE_WARNING = "suppressContentEditableWarning";
const SUPPRESS_HYDRATION_WARNING = "suppressHydrationWarning";
const AUTOFOCUS = "autoFocus";
const CHILDREN = "children";
const STYLE = "style";
const HTML = "__html";
const DEPRECATED_flareListeners = "DEPRECATED_flareListeners";
const registrationNameModules = {};
for (const propKey in nextProps) {
if(! nextProps.hasOwnProperty(propKey)) {continue;
}
const nextProp = nextProps[propKey];
if (propKey === STYLE) {
if (__DEV__) {
if (nextProp) {
// Freeze the next style object so that we can assume it won't be
// mutated. We have already warned for this in the past.
Object.freeze(nextProp); }}// Relies on `updateStylesByID` not mutating `styleUpdates`.
setValueForStyles(domElement, nextProp);
} else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
const nextHtml = nextProp ? nextProp[HTML] : undefined;
if(nextHtml ! =null) { setInnerHTML(domElement, nextHtml); }}else if (propKey === CHILDREN) {
if (typeof nextProp === "string") {
// Avoid setting initial textContent when the text is empty. In IE11 setting
// textContent on a <textarea> will cause the placeholder to not
// show within the <textarea> until it has been focused and blurred again.
// https://github.com/facebook/react/issues/6731#issuecomment-254874553
constcanSetTextContent = tag ! = ="textarea"|| nextProp ! = ="";
if(canSetTextContent) { setTextContent(domElement, nextProp); }}else if (typeof nextProp === "number") {
setTextContent(domElement, ""+ nextProp); }}else if (
(enableDeprecatedFlareAPI && propKey === DEPRECATED_flareListeners) ||
propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
propKey === SUPPRESS_HYDRATION_WARNING
) {
// Noop
} else if (propKey === AUTOFOCUS) {
// We polyfill it separately on the client during commit.
// We could have excluded it in the property list instead of
// adding a special case here, but then it wouldn't be emitted
// on server rendering (but we *do* want to emit it in SSR).
} else if (registrationNameModules.hasOwnProperty(propKey)) {
if(nextProp ! =null) {
if (__DEV__ && typeofnextProp ! = ="function") { warnForInvalidEventListener(propKey, nextProp); } ensureListeningTo(rootContainerElement, propKey); }}else if(nextProp ! =null) { setValueForProperty( domElement, propKey, nextProp, isCustomComponentTag ); }}}function isCustomComponent(tagName, props) {
if (tagName.indexOf("-") = = =- 1) {
return typeof props.is === "string";
}
switch (tagName) {
// These are reserved SVG and MathML elements.
// We don't mind this whitelist too much because we expect it to never grow.
// The alternative is to track the namespace in a few places which is convoluted.
// https://w3c.github.io/webcomponents/spec/custom/#custom-elements-core-concepts
case "annotation-xml":
case "color-profile":
case "font-face":
case "font-face-src":
case "font-face-uri":
case "font-face-format":
case "font-face-name":
case "missing-glyph":
return false;
default:
return true; }}function shouldAutoFocusHostComponent(type, props) {
switch (type) {
case "button":
case "input":
case "select":
case "textarea":
return!!!!! props.autoFocus; }return false;
}
function markRef(workInProgress) {
workInProgress.effectTag |= Ref;
}
const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case IndeterminateComponent:
case LazyComponent:
case SimpleMemoComponent:
case FunctionComponent:
case ForwardRef:
case Fragment:
case Mode:
case Profiler:
case ContextConsumer:
case MemoComponent:
return null;
case ClassComponent: {
const Component = workInProgress.type;
if (isLegacyContextProvider(Component)) {
popLegacyContext(workInProgress);
}
return null;
}
case HostRoot: {
popHostContainer(workInProgress);
popTopLevelLegacyContextObject(workInProgress);
resetMutableSourceWorkInProgressVersions();
const fiberRoot = workInProgress.stateNode;
if (fiberRoot.pendingContext) {
fiberRoot.context = fiberRoot.pendingContext;
fiberRoot.pendingContext = null;
}
if (current === null || current.child === null) {
// If we hydrated, pop so that we can delete any remaining children
// that weren't hydrated.
const wasHydrated = popHydrationState(workInProgress);
if (wasHydrated) {
// If we hydrated, then we'll need to schedule an update for
// the commit side-effects on the root.
markUpdate(workInProgress);
} else if(! fiberRoot.hydrate) {// Schedule an effect to clear this container at the start of the next commit.
// This handles the case of React rendering into a container with previous children.
// It's also safe to do for updates too, because current.child would only be null
// if the previous render was null (so the the container would already be empty).
workInProgress.effectTag |= Snapshot;
}
}
updateHostContainer(workInProgress);
return null;
}
case HostComponent: {
popHostContext(workInProgress);
const rootContainerInstance = getRootHostContainer();
const type = workInProgress.type;
if(current ! = =null&& workInProgress.stateNode ! =null) {
updateHostComponent(
current,
workInProgress,
type,
newProps,
rootContainerInstance
);
if (enableDeprecatedFlareAPI) {
const prevListeners = current.memoizedProps.DEPRECATED_flareListeners;
const nextListeners = newProps.DEPRECATED_flareListeners;
if (prevListeners !== nextListeners) {
markUpdate(workInProgress);
}
}
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
} else {
if(! newProps) { invariant( workInProgress.stateNode ! = =null."We must have new props for new mounts. This error is likely " +
"caused by a bug in React. Please file an issue."
);
// This can happen when we abort work.
return null;
}
const currentHostContext = getHostContext();
// TODO: Move createInstance to beginWork and keep it on a context
// "stack" as the parent. Then append children as we go in beginWork
// or completeWork depending on whether we want to add them top->down or
// bottom->up. Top->down is faster in IE11.
const wasHydrated = popHydrationState(workInProgress);
if (wasHydrated) {
// TODO: Move this and createInstance step into the beginPhase
// to consolidate.
if (
prepareToHydrateHostInstance(
workInProgress,
rootContainerInstance,
currentHostContext
)
) {
// If changes to the hydrated node need to be applied at the
// commit-phase we mark this as such.
markUpdate(workInProgress);
}
if (enableDeprecatedFlareAPI) {
const listeners = newProps.DEPRECATED_flareListeners;
if(listeners ! =null) { updateDeprecatedEventListeners( listeners, workInProgress, rootContainerInstance ); }}}else {
const instance = createInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress
);
// fixme
appendAllChildren(instance, workInProgress, false.false);
// This needs to be set before we mount Flare event listeners
workInProgress.stateNode = instance;
if (enableDeprecatedFlareAPI) {
const listeners = newProps.DEPRECATED_flareListeners;
if(listeners ! =null) { updateDeprecatedEventListeners( listeners, workInProgress, rootContainerInstance ); }}// Certain renderers require commit-time effects for initial mount.
// (eg DOM renderer supports auto-focus for certain elements).
// Make sure such renderers get scheduled for later work.
if( finalizeInitialChildren( instance, type, newProps, rootContainerInstance, currentHostContext ) ) { markUpdate(workInProgress); }}if(workInProgress.ref ! = =null) {
// If there is a ref on a host node we need to schedule a callbackmarkRef(workInProgress); }}return null;
}
case HostText: {
const newText = newProps;
if(current && workInProgress.stateNode ! =null) {
const oldText = current.memoizedProps;
// If we have an alternate, that means this is an update and we need
// to schedule a side-effect to do the updates.
updateHostText(current, workInProgress, oldText, newText);
} else {
if (typeofnewText ! = ="string") { invariant( workInProgress.stateNode ! = =null."We must have new props for new mounts. This error is likely " +
"caused by a bug in React. Please file an issue."
);
// This can happen when we abort work.
}
const rootContainerInstance = getRootHostContainer();
const currentHostContext = getHostContext();
const wasHydrated = popHydrationState(workInProgress);
if (wasHydrated) {
if(prepareToHydrateHostTextInstance(workInProgress)) { markUpdate(workInProgress); }}else{ workInProgress.stateNode = createTextInstance( newText, rootContainerInstance, currentHostContext, workInProgress ); }}return null;
}
case SuspenseComponent: {
popSuspenseContext(workInProgress);
const nextState = workInProgress.memoizedState;
if (enableSuspenseServerRenderer) {
if(nextState ! = =null&& nextState.dehydrated ! = =null) {
if (current === null) {
const wasHydrated = popHydrationState(workInProgress);
invariant(
wasHydrated,
"A dehydrated suspense component was completed without a hydrated node. " +
"This is probably a bug in React."
);
prepareToHydrateHostSuspenseInstance(workInProgress);
if (enableSchedulerTracing) {
markSpawnedWork(OffscreenLane);
}
return null;
} else {
// We should never have been in a hydration state if we didn't have a current.
// However, in some of those paths, we might have reentered a hydration state
// and then we might be inside a hydration state. In that case, we'll need to exit out of it.
resetHydrationState();
if ((workInProgress.effectTag & DidCapture) === NoEffect) {
// This boundary did not suspend so it's now hydrated and unsuspended.
workInProgress.memoizedState = null;
}
// If nothing suspended, we need to schedule an effect to mark this boundary
// as having hydrated so events know that they're free to be invoked.
// It's also a signal to replay events and the suspense callback.
// If something suspended, schedule an effect to attach retry listeners.
// So we might as well always mark this.
workInProgress.effectTag |= Update;
return null; }}}if((workInProgress.effectTag & DidCapture) ! == NoEffect) {// Something suspended. Re-render with the fallback children.
workInProgress.lanes = renderLanes;
// Do not reset the effect list.
return workInProgress;
}
constnextDidTimeout = nextState ! = =null;
let prevDidTimeout = false;
if (current === null) {
if(workInProgress.memoizedProps.fallback ! = =undefined) { popHydrationState(workInProgress); }}else {
constprevState = current.memoizedState; prevDidTimeout = prevState ! = =null;
}
if(nextDidTimeout && ! prevDidTimeout) {// If this subtreee is running in blocking mode we can suspend,
// otherwise we won't suspend.
// TODO: This will still suspend a synchronous tree if anything
// in the concurrent tree already suspended during this render.
// This is a known bug.
if((workInProgress.mode & BlockingMode) ! == NoMode) {// TODO: Move this back to throwException because this is too late
// if this is a large tree which is common for initial loads. We
// don't know if we should restart a render or not until we get
// this marker, and this is too late.
// If this render already had a ping or lower pri updates,
// and this is the first time we know we're going to suspend we
// should be able to immediately restart from within throwException.
const hasInvisibleChildContext =
current === null&& workInProgress.memoizedProps.unstable_avoidThisFallback ! = =true;
if (
hasInvisibleChildContext ||
hasSuspenseContext(
suspenseStackCursor.current,
InvisibleParentSuspenseContext
)
) {
// If this was in an invisible tree or a new render, then showing
// this boundary is ok.
renderDidSuspend();
} else {
// Otherwise, we're going to have to hide content so we should
// suspend for longer if possible.renderDidSuspendDelayIfPossible(); }}}if (supportsPersistence) {
// TODO: Only schedule updates if not prevDidTimeout.
if (nextDidTimeout) {
// If this boundary just timed out, schedule an effect to attach a
// retry listener to the promise. This flag is also used to hide the
// primary children.workInProgress.effectTag |= Update; }}if (supportsMutation) {
// TODO: Only schedule updates if these values are non equal, i.e. it changed.
if (nextDidTimeout || prevDidTimeout) {
// If this boundary just timed out, schedule an effect to attach a
// retry listener to the promise. This flag is also used to hide the
// primary children. In mutation mode, we also need the flag to
// *unhide* children that were previously hidden, so check if this
// is currently timed out, too.workInProgress.effectTag |= Update; }}if( enableSuspenseCallback && workInProgress.updateQueue ! = =null&& workInProgress.memoizedProps.suspenseCallback ! =null
) {
// Always notify the callback
workInProgress.effectTag |= Update;
}
return null;
}
case HostPortal:
popHostContainer(workInProgress);
updateHostContainer(workInProgress);
if (current === null) {
preparePortalMount(workInProgress.stateNode.containerInfo);
}
return null;
case ContextProvider:
// Pop provider fiber
popProvider(workInProgress);
return null;
case IncompleteClassComponent: {
// Same as class component case. I put it down here so that the tags are
// sequential to ensure this switch is compiled to a jump table.
const Component = workInProgress.type;
if (isLegacyContextProvider(Component)) {
popLegacyContext(workInProgress);
}
return null;
}
case SuspenseListComponent: {
popSuspenseContext(workInProgress);
const renderState = workInProgress.memoizedState;
if (renderState === null) {
// We're running in the default, "independent" mode.
// We don't do anything in this mode.
return null;
}
letdidSuspendAlready = (workInProgress.effectTag & DidCapture) ! == NoEffect;const renderedTail = renderState.rendering;
if (renderedTail === null) {
// We just rendered the head.
if(! didSuspendAlready) {// This is the first pass. We need to figure out if anything is still
// suspended in the rendered set.
// If new content unsuspended, but there's still some content that
// didn't. Then we need to do a second pass that forces everything
// to keep showing their fallbacks.
// We might be suspended if something in this render pass suspended, or
// something in the previous committed pass suspended. Otherwise,
// there's no chance so we can skip the expensive call to
// findFirstSuspended.
const cannotBeSuspended =
renderHasNotSuspendedYet() &&
(current === null || (current.effectTag & DidCapture) === NoEffect);
if(! cannotBeSuspended) {let row = workInProgress.child;
while(row ! = =null) {
const suspended = findFirstSuspended(row);
if(suspended ! = =null) {
didSuspendAlready = true;
workInProgress.effectTag |= DidCapture;
cutOffTailIfNeeded(renderState, false);
// If this is a newly suspended tree, it might not get committed as
// part of the second pass. In that case nothing will subscribe to
// its thennables. Instead, we'll transfer its thennables to the
// SuspenseList so that it can retry if they resolve.
// There might be multiple of these in the list but since we're
// going to wait for all of them anyway, it doesn't really matter
// which ones gets to ping. In theory we could get clever and keep
// track of how many dependencies remain but it gets tricky because
// in the meantime, we can add/remove/change items and dependencies.
// We might bail out of the loop before finding any but that
// doesn't matter since that means that the other boundaries that
// we did find already has their listeners attached.
const newThennables = suspended.updateQueue;
if(newThennables ! = =null) {
workInProgress.updateQueue = newThennables;
workInProgress.effectTag |= Update;
}
// Rerender the whole list, but this time, we'll force fallbacks
// to stay in place.
// Reset the effect list before doing the second pass since that's now invalid.
if (renderState.lastEffect === null) {
workInProgress.firstEffect = null;
}
workInProgress.lastEffect = renderState.lastEffect;
// Reset the child fibers to their original state.
resetChildFibers(workInProgress, renderLanes);
// Set up the Suspense Context to force suspense and immediately
// rerender the children.
pushSuspenseContext(
workInProgress,
setShallowSuspenseContext(
suspenseStackCursor.current,
ForceSuspenseFallback
)
);
returnworkInProgress.child; } row = row.sibling; }}}else {
cutOffTailIfNeeded(renderState, false);
}
// Next we're going to render the tail.
} else {
// Append the rendered row to the child list.
if(! didSuspendAlready) {const suspended = findFirstSuspended(renderedTail);
if(suspended ! = =null) {
workInProgress.effectTag |= DidCapture;
didSuspendAlready = true;
// Ensure we transfer the update queue to the parent so that it doesn't
// get lost if this row ends up dropped during a second pass.
const newThennables = suspended.updateQueue;
if(newThennables ! = =null) {
workInProgress.updateQueue = newThennables;
workInProgress.effectTag |= Update;
}
cutOffTailIfNeeded(renderState, true);
// This might have been modified.
if (
renderState.tail === null &&
renderState.tailMode === "hidden" &&
!renderedTail.alternate
) {
// We need to delete the row we just rendered.
// Reset the effect list to what it was before we rendered this
// child. The nested children have already appended themselves.
const lastEffect = (workInProgress.lastEffect =
renderState.lastEffect);
// Remove any effects that were appended after this point.
if(lastEffect ! = =null) {
lastEffect.nextEffect = null;
}
// We're done.
return null; }}else if (
// The time it took to render last row is greater than time until
// the expiration.
now() * 2- renderState.renderingStartTime > renderState.tailExpiration && renderLanes ! == OffscreenLane ) {// We have now passed our CPU deadline and we'll just give up further
// attempts to render the main content and only render fallbacks.
// The assumption is that this is usually faster.
workInProgress.effectTag |= DidCapture;
didSuspendAlready = true;
cutOffTailIfNeeded(renderState, false);
// Since nothing actually suspended, there will nothing to ping this
// to get it started back up to attempt the next item. If we can show
// them, then they really have the same priority as this render.
// So we'll pick it back up the very next render pass once we've had
// an opportunity to yield for paint.
workInProgress.lanes = renderLanes;
if(enableSchedulerTracing) { markSpawnedWork(renderLanes); }}}if (renderState.isBackwards) {
// The effect list of the backwards tail will have been added
// to the end. This breaks the guarantee that life-cycles fire in
// sibling order but that isn't a strong guarantee promised by React.
// Especially since these might also just pop in during future commits.
// Append to the beginning of the list.
renderedTail.sibling = workInProgress.child;
workInProgress.child = renderedTail;
} else {
const previousSibling = renderState.last;
if(previousSibling ! = =null) {
previousSibling.sibling = renderedTail;
} else{ workInProgress.child = renderedTail; } renderState.last = renderedTail; }}if(renderState.tail ! = =null) {
// We still have tail rows to render.
if (renderState.tailExpiration === 0) {
// Heuristic for how long we're willing to spend rendering rows
// until we just give up and show what we have so far.
const TAIL_EXPIRATION_TIMEOUT_MS = 500;
renderState.tailExpiration = now() + TAIL_EXPIRATION_TIMEOUT_MS;
// TODO: This is meant to mimic the train model or JND but this
// is a per component value. It should really be since the start
// of the total render or last commit. Consider using something like
// globalMostRecentFallbackTime. That doesn't account for being
// suspended for part of the time or when it's a new render.
// It should probably use a global start time value instead.
}
// Pop a row.
const next = renderState.tail;
renderState.rendering = next;
renderState.tail = next.sibling;
renderState.lastEffect = workInProgress.lastEffect;
renderState.renderingStartTime = now();
next.sibling = null;
// Restore the context.
// TODO: We can probably just avoid popping it instead and only
// setting it the first time we go from not suspended to suspended.
let suspenseContext = suspenseStackCursor.current;
if (didSuspendAlready) {
suspenseContext = setShallowSuspenseContext(
suspenseContext,
ForceSuspenseFallback
);
} else {
suspenseContext = setDefaultShallowSuspenseContext(suspenseContext);
}
pushSuspenseContext(workInProgress, suspenseContext);
// Do a pass over the next row.
return next;
}
return null;
}
case FundamentalComponent: {
if (enableFundamentalAPI) {
const fundamentalImpl = workInProgress.type.impl;
let fundamentalInstance = workInProgress.stateNode;
if (fundamentalInstance === null) {
const getInitialState = fundamentalImpl.getInitialState;
let fundamentalState;
if(getInitialState ! = =undefined) {
fundamentalState = getInitialState(newProps);
}
fundamentalInstance = workInProgress.stateNode = createFundamentalStateInstance(
workInProgress,
newProps,
fundamentalImpl,
fundamentalState || {}
);
const instance = getFundamentalComponentInstance(fundamentalInstance);
fundamentalInstance.instance = instance;
if (fundamentalImpl.reconcileChildren === false) {
return null;
}
appendAllChildren(instance, workInProgress, false.false);
mountFundamentalComponent(fundamentalInstance);
} else {
// We fire update in commit phase
const prevProps = fundamentalInstance.props;
fundamentalInstance.prevProps = prevProps;
fundamentalInstance.props = newProps;
fundamentalInstance.currentFiber = workInProgress;
if (supportsPersistence) {
const instance = cloneFundamentalInstance(fundamentalInstance);
fundamentalInstance.instance = instance;
appendAllChildren(instance, workInProgress, false.false);
}
const shouldUpdate = shouldUpdateFundamentalComponent(
fundamentalInstance
);
if(shouldUpdate) { markUpdate(workInProgress); }}return null;
}
break;
}
case ScopeComponent: {
if (enableScopeAPI) {
if (current === null) {
const scopeInstance = createScopeInstance();
workInProgress.stateNode = scopeInstance;
if (enableDeprecatedFlareAPI) {
const listeners = newProps.DEPRECATED_flareListeners;
if(listeners ! =null) {
const rootContainerInstance = getRootHostContainer();
updateDeprecatedEventListeners(
listeners,
workInProgress,
rootContainerInstance
);
}
}
prepareScopeUpdate(scopeInstance, workInProgress);
if(workInProgress.ref ! = =null) { markRef(workInProgress); markUpdate(workInProgress); }}else {
if (enableDeprecatedFlareAPI) {
const prevListeners =
current.memoizedProps.DEPRECATED_flareListeners;
const nextListeners = newProps.DEPRECATED_flareListeners;
if( prevListeners ! == nextListeners || workInProgress.ref ! = =null) { markUpdate(workInProgress); }}else {
if(workInProgress.ref ! = =null) { markUpdate(workInProgress); }}if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
}
return null;
}
break;
}
case Block:
if (enableBlocksAPI) {
return null;
}
break;
case OffscreenComponent:
case LegacyHiddenComponent: {
popRenderLanes(workInProgress);
if(current ! = =null) {
const nextState = workInProgress.memoizedState;
const prevState = current.memoizedState;
constprevIsHidden = prevState ! = =null;
constnextIsHidden = nextState ! = =null;
if (prevIsHidden !== nextIsHidden) {
workInProgress.effectTag |= Update;
}
}
return null;
}
}
invariant(
false."Unknown unit of work tag (%s). This error is likely caused by a bug in " +
"React. Please file an issue.",
workInProgress.tag
);
}
Copy the code
Summary: \
function resetChildLanes(completedWork) {
if( (completedWork.tag === LegacyHiddenComponent || completedWork.tag === OffscreenComponent) && completedWork.memoizedState ! = =null &&
!includesSomeLane(subtreeRenderLanes, OffscreenLane)
) {
// The child components of this component are hidden, do not bubble their expiration time
return;
}
let newChildLanes = NoLanes;
if(enableProfilerTimer && (completedWork.mode & ProfileMode) ! == NoMode) {// false
let actualDuration = completedWork.actualDuration;
let treeBaseDuration = completedWork.selfBaseDuration;
const shouldBubbleActualDurations =
completedWork.alternate === null|| completedWork.child ! == completedWork.alternate.child;let child = completedWork.child;
while(child ! = =null) {
newChildLanes = mergeLanes(
newChildLanes,
mergeLanes(child.lanes, child.childLanes)
);
if (shouldBubbleActualDurations) {
actualDuration += child.actualDuration;
}
treeBaseDuration += child.treeBaseDuration;
child = child.sibling;
}
completedWork.actualDuration = actualDuration;
completedWork.treeBaseDuration = treeBaseDuration;
} else {
Merge the lanes of all sibling nodes and their children
let child = completedWork.child;
while(child ! = =null) { newChildLanes = mergeLanes( newChildLanes, mergeLanes(child.lanes, child.childLanes) ); child = child.sibling; }}// Reset childLanes on the current fiber node
completedWork.childLanes = newChildLanes;
}
Copy the code
Context-dependent, involving the ClassComponent context, not yet delved into
6 commitRoot
At the end of the initial render commit phase, call the lifecycle method of the node in the side effects list, and append the entire DOM tree stored in memory to root container.
function commitRoot(root) {
const renderPriorityLevel = getCurrentPriorityLevel();
runWithPriority(
ImmediatePriority,
commitRootImpl.bind(null, root, renderPriorityLevel)
);
return null;
}
function markRootFinished(root, remainingLanes) {
root.pendingLanes = remainingLanes;
// Let's try again
root.suspendedLanes = 0;
root.pingedLanes = 0;
root.expiredLanes &= remainingLanes;
root.mutableReadLanes &= remainingLanes;
}
function commitRootImpl(root, renderPriorityLevel) {
do {
// `flushPassiveEffects` will call `flushSyncUpdateQueue` at the end, which
// means `flushPassiveEffects` will sometimes result in additional
// passive effects. So we need to keep flushing in a loop until there are
// no more pending effects.
// TODO: Might be better if `flushPassiveEffects` did not automatically
// flush synchronous work at the end, to avoid factoring hazards like this.
flushPassiveEffects();
} while(rootWithPendingPassiveEffects ! = =null);
const finishedWork = root.finishedWork;
const lanes = root.finishedLanes;
if (finishedWork === null) {
return null;
}
root.finishedWork = null;
root.finishedLanes = NoLanes;
// commitRoot never returns; It's always done synchronously
// Therefore, we can now clear these to allow a new callback to be scheduled.
root.callbackNode = null;
root.callbackId = NoLanes;
// TODO: Use LanePriority instead of SchedulerPriority
if (renderPriorityLevel < ImmediatePriority) {
// If this is concurrent rendering, the expiration time can be reset.
root.expiresAt = - 1;
}
// Update the first and last pending times on root.
// The last pending time is the remaining time of the root fiber
let remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes);
markRootFinished(root, remainingLanes);
if(rootsWithPendingDiscreteUpdates ! = =null) {
if(! hasDiscreteLanes(remainingLanes) && rootsWithPendingDiscreteUpdates.has(root) ) { rootsWithPendingDiscreteUpdates.delete(root); }}if (root === workInProgressRoot) {
// Now you can reset it
workInProgressRoot = null;
workInProgress = null;
workInProgressRootRenderLanes = NoLanes;
} else {
// This indicates that the last root we worked on is not the same one that
// we're committing now. This most commonly happens when a suspended root
// times out.
}
// Get the list of side effects
let firstEffect;
if (finishedWork.effectTag > PerformedWork) {
// Fiber's side effects list contains only children that have side effects, not itself.
// So root has a side effect that we need to add to the end of the list.
// The final list is in the order of the children before the parents, and all the side effects in the Fiber tree are contained in this list
if(finishedWork.lastEffect ! = =null) {
finishedWork.lastEffect.nextEffect = finishedWork;
firstEffect = finishedWork.firstEffect;
} else{ firstEffect = finishedWork; }}else {
// root has no side effects
firstEffect = finishedWork.firstEffect;
}
if(firstEffect ! = =null) {
const prevExecutionContext = executionContext;
executionContext |= CommitContext;
const prevInteractions = pushInteractions(root);
// Reset this value to NULL before the call life cycle
ReactCurrentOwner.current = null;
// The COMMIT phase is divided into several sub-phases.
// We need to do a corresponding processing for each node side effect list:
// All mutation effects take precedence over Layout Effects
// The first stage is called the "before mutation" stage (mutate is the DOM tree host tree that has been built and stored in memory but not appended to the root container)
We use this phase to read the state of the host tree before we mutate it
// getSnapshotBeforeUpdate is called at this stage
focusedInstanceHandle = prepareForCommit(root.containerInfo);
shouldFireAfterActiveInstanceBlur = false;
nextEffect = firstEffect;
do {
if (__DEV__) {
invokeGuardedCallback(null, commitBeforeMutationEffects, null);
if(hasCaughtError()) { invariant(nextEffect ! = =null."Should be working on an effect.");
consterror = clearCaughtError(); captureCommitPhaseError(nextEffect, error); nextEffect = nextEffect.nextEffect; }}else {
try {
commitBeforeMutationEffects();
} catch (error) {
console.error(error); invariant(nextEffect ! = =null."Should be working on an effect."); captureCommitPhaseError(nextEffect, error); nextEffect = nextEffect.nextEffect; }}}while(nextEffect ! = =null);
// We no longer need to track the active instance fiber
focusedInstanceHandle = null;
if (enableProfilerTimer) {
// Mark the current commit time to be shared by all Profilers in this
// batch. This enables them to be grouped later.
recordCommitTime();
}
// The next stage is the "mutation" stage, where we append the host tree to the root container
/ / important! Only at this stage will react render effects appear in the browser
nextEffect = firstEffect;
do {
if (__DEV__) {
invokeGuardedCallback(
null,
commitMutationEffects,
null,
root,
renderPriorityLevel
);
if(hasCaughtError()) { invariant(nextEffect ! = =null."Should be working on an effect.");
consterror = clearCaughtError(); captureCommitPhaseError(nextEffect, error); nextEffect = nextEffect.nextEffect; }}else {
try {
commitMutationEffects(root, renderPriorityLevel);
} catch (error) {
console.error(error); invariant(nextEffect ! = =null."Should be working on an effect."); captureCommitPhaseError(nextEffect, error); nextEffect = nextEffect.nextEffect; }}}while(nextEffect ! = =null);
if (shouldFireAfterActiveInstanceBlur) {
afterActiveInstanceBlur();
}
resetAfterCommit(root.containerInfo);
// The work-in-progress tree is now the current tree. This must come after
// the mutation phase, so that the previous tree is still current during
// componentWillUnmount, but before the layout phase, so that the finished
// work is current during componentDidMount/Update.
root.current = finishedWork;
// The next stage is the "Layout" stage, where we call the side effects method after the host tree is mounted into the container.
// The convention at this stage is for layout, but for legacy reasons, the class component lifecycle is also triggered.
nextEffect = firstEffect;
do {
if (__DEV__) {
invokeGuardedCallback(null, commitLayoutEffects, null, root, lanes);
if(hasCaughtError()) { invariant(nextEffect ! = =null."Should be working on an effect.");
consterror = clearCaughtError(); captureCommitPhaseError(nextEffect, error); nextEffect = nextEffect.nextEffect; }}else {
try {
commitLayoutEffects(root, lanes);
} catch (error) {
console.error(error); invariant(nextEffect ! = =null."Should be working on an effect."); captureCommitPhaseError(nextEffect, error); nextEffect = nextEffect.nextEffect; }}}while(nextEffect ! = =null);
nextEffect = null;
// Tell Scheduler to yield at the end of the frame, so the browser has an
// opportunity to paint.
// requestPaint();
if (enableSchedulerTracing) {
popInteractions(prevInteractions);
}
executionContext = prevExecutionContext;
} else {
// No effects.
root.current = finishedWork;
// Measure these anyway so the flamegraph explicitly shows that there were
// no effects.
// TODO: Maybe there's a better way to report this.
if(enableProfilerTimer) { recordCommitTime(); }}}Copy the code
6.1 commitBeforeMutationEffects
let focusedInstanceHandle = null;
let shouldFireAfterActiveInstanceBlur = false;
function commitBeforeMutationEffects() {
while(nextEffect ! = =null) {
if(! shouldFireAfterActiveInstanceBlur && focusedInstanceHandle ! = =null &&
isFiberHiddenOrDeletedAndContains(nextEffect, focusedInstanceHandle)
) {
shouldFireAfterActiveInstanceBlur = true;
beforeActiveInstanceBlur();
}
const effectTag = nextEffect.effectTag;
if((effectTag & Snapshot) ! == NoEffect) {const current = nextEffect.alternate;
commitBeforeMutationEffectOnFiber(current, nextEffect);
}
if((effectTag & Passive) ! == NoEffect) {// If there are passive effects, schedule a callback to flush at
// the earliest opportunity.
if(! rootDoesHavePassiveEffects) { rootDoesHavePassiveEffects =true;
scheduleCallback(NormalSchedulerPriority, () => {
flushPassiveEffects();
return null; }); } } nextEffect = nextEffect.nextEffect; }}function commitBeforeMutationEffectOnFiber(current, finishedWork) {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent:
case Block: {
return;
}
case ClassComponent: {
if (finishedWork.effectTag & Snapshot) {
if(current ! = =null) {
const prevProps = current.memoizedProps;
const prevState = current.memoizedState;
const instance = finishedWork.stateNode;
// We could update instance props and state here,
// but instead we rely on them being set during last render.
// TODO: revisit this when we implement resuming.
if (__DEV__) {
// ,,,
}
// Life Cycle three (getSnapshotBeforeUpdate) : process before mounting the real DOM, often used for list expansion
const snapshot = instance.getSnapshotBeforeUpdate(
finishedWork.elementType === finishedWork.type
? prevProps
: resolveDefaultProps(finishedWork.type, prevProps),
prevState
);
if (__DEV__) {
const didWarnSet = didWarnAboutUndefinedSnapshotBeforeUpdate;
if (snapshot === undefined && !didWarnSet.has(finishedWork.type)) {
didWarnSet.add(finishedWork.type);
console.error(
"%s.getSnapshotBeforeUpdate(): A snapshot value (or null) " +
"must be returned. You have returned undefined.", getComponentName(finishedWork.type) ); } } instance.__reactInternalSnapshotBeforeUpdate = snapshot; }}return;
}
case HostRoot: {
if (supportsMutation) {
if (finishedWork.effectTag & Snapshot) {
constroot = finishedWork.stateNode; clearContainer(root.containerInfo); }}return;
}
case HostComponent:
case HostText:
case HostPortal:
case IncompleteClassComponent:
// Nothing to do for these component types
return;
}
invariant(
false."This unit of work tag should not have side-effects. This error is " +
"likely caused by a bug in React. Please file an issue."
);
}
Copy the code
6.2 commitMutationEffects
Append the built DOM tree to the root container
function commitMutationEffects(root, renderPriorityLevel) {
// TODO: Should probably move the bulk of this function to commitWork.
while(nextEffect ! = =null) {
const effectTag = nextEffect.effectTag;
if (effectTag & ContentReset) {
commitResetTextContent(nextEffect);
}
if (effectTag & Ref) {
const current = nextEffect.alternate;
if(current ! = =null) {
// Clear the ref property of the current node to nullcommitDetachRef(current); }}// The following switch statement is only concerned about placement,
// updates, and deletions. To avoid needing to add a case for every possible
// bitmap value, we remove the secondary effects from the effect tag and
// switch on that value.
const primaryEffectTag =
effectTag & (Placement | Update | Deletion | Hydrating);
switch (primaryEffectTag) {
case Placement: {
commitPlacement(nextEffect);
// Clear the "placement" from effect tag so that we know that this is
// inserted, before any life-cycles like componentDidMount gets called.
// TODO: findDOMNode doesn't rely on this any more but isMounted does
// and isMounted is deprecated anyway so we should be able to kill this.
nextEffect.effectTag &= ~Placement;
break;
}
case PlacementAndUpdate: {
// Placement
commitPlacement(nextEffect);
// Clear the "placement" from effect tag so that we know that this is
// inserted, before any life-cycles like componentDidMount gets called.
nextEffect.effectTag &= ~Placement;
// Update
const current = nextEffect.alternate;
commitWork(current, nextEffect);
break;
}
case Hydrating: {
nextEffect.effectTag &= ~Hydrating;
break;
}
case HydratingAndUpdate: {
nextEffect.effectTag &= ~Hydrating;
// Update
const current = nextEffect.alternate;
commitWork(current, nextEffect);
break;
}
case Update: {
const current = nextEffect.alternate;
commitWork(current, nextEffect);
break;
}
case Deletion: {
commitDeletion(root, nextEffect, renderPriorityLevel);
break; } } nextEffect = nextEffect.nextEffect; }}function commitResetTextContent(current) {
if(! supportsMutation) {return;
}
resetTextContent(current.stateNode);
}
function resetTextContent(domElement) {
setTextContent(domElement, "");
}
const setTextContent = function (node, text) {
if (text) {
const firstChild = node.firstChild;
if (
firstChild &&
firstChild === node.lastChild &&
firstChild.nodeType === TEXT_NODE
) {
firstChild.nodeValue = text;
return;
}
}
node.textContent = text;
};
function commitDetachRef(current) {
const currentRef = current.ref;
if(currentRef ! = =null) {
if (typeof currentRef === "function") {
currentRef(null);
} else {
currentRef.current = null; }}}function commitPlacement(finishedWork) {
if(! supportsMutation) {return;
}
// Recursively insert all host nodes into the parent.
const parentFiber = getHostParentFiber(finishedWork);
let parent;
let isContainer;
const parentStateNode = parentFiber.stateNode;
switch (parentFiber.tag) {
case HostComponent:
parent = parentStateNode;
isContainer = false;
break;
case HostRoot:
// Only the root node sets isContainer to true
parent = parentStateNode.containerInfo;
isContainer = true;
break;
case HostPortal:
parent = parentStateNode.containerInfo;
isContainer = true;
break;
case FundamentalComponent:
if (enableFundamentalAPI) {
parent = parentStateNode.instance;
isContainer = false;
}
// eslint-disable-next-line-no-fallthrough
default:
invariant(
false."Invalid host parent fiber. This error is likely caused by a bug " +
"in React. Please file an issue."
);
}
if (parentFiber.effectTag & ContentReset) {
// Reset the parent text content before inserting anything
resetTextContent(parent);
// The ContentReset bit is cleared in the effect tag, indicating that it has already been processed and no further action is required
parentFiber.effectTag &= ~ContentReset;
}
const before = getHostSibling(finishedWork);
// We only have the top Fiber that was inserted but we need to recurse down its
// children to find all the terminal nodes.
if (isContainer) {
insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent);
} else{ insertOrAppendPlacementNode(finishedWork, before, parent); }}function insertOrAppendPlacementNodeIntoContainer(node, before, parent) {
const { tag } = node;
const isHost = tag === HostComponent || tag === HostText;
if (isHost || (enableFundamentalAPI && tag === FundamentalComponent)) {
const stateNode = isHost ? node.stateNode : node.stateNode.instance;
if (before) {
insertInContainerBefore(parent, stateNode, before);
} else {
// trueappendChildToContainer(parent, stateNode); }}else if (tag === HostPortal) {
// If the insertion itself is a portal, then we don't want to traverse
// down its children. Instead, we'll get insertions from each child in
// the portal directly.
} else {
const child = node.child;
if(child ! = =null) {
insertOrAppendPlacementNodeIntoContainer(child, before, parent);
let sibling = child.sibling;
while(sibling ! = =null) { insertOrAppendPlacementNodeIntoContainer(sibling, before, parent); sibling = sibling.sibling; }}}}function insertOrAppendPlacementNode(node, before, parent) {
const { tag } = node;
const isHost = tag === HostComponent || tag === HostText;
if (isHost || (enableFundamentalAPI && tag === FundamentalComponent)) {
const stateNode = isHost ? node.stateNode : node.stateNode.instance;
if (before) {
insertBefore(parent, stateNode, before);
} else{ appendChild(parent, stateNode); }}else if (tag === HostPortal) {
// If the insertion itself is a portal, then we don't want to traverse
// down its children. Instead, we'll get insertions from each child in
// the portal directly.
} else {
const child = node.child;
if(child ! = =null) {
insertOrAppendPlacementNode(child, before, parent);
let sibling = child.sibling;
while(sibling ! = =null) { insertOrAppendPlacementNode(sibling, before, parent); sibling = sibling.sibling; }}}}/ / there before
function insertInContainerBefore(container, child, beforeChild) {
if (container.nodeType === COMMENT_NODE) {
container.parentNode.insertBefore(child, beforeChild);
} else{ container.insertBefore(child, beforeChild); }}/ / not before
function appendChildToContainer(container, child) {
let parentNode;
if (container.nodeType === COMMENT_NODE) {
parentNode = container.parentNode;
parentNode.insertBefore(child, container);
} else {
// true
parentNode = container;
// Finally mount, at which point the effect is displayed on the browser.
parentNode.appendChild(child);
}
// This container might be used for a portal.
// If something inside a portal is clicked, that click should bubble
// through the React tree. However, on Mobile Safari the click would
// never bubble through the *DOM* tree unless an ancestor with onclick
// event exists. So we wouldn't see it and dispatch it.
// This is why we ensure that non React root containers have inline onclick
// defined.
// https://github.com/facebook/react/issues/11918
const reactRootContainer = container._reactRootContainer;
if (
(reactRootContainer === null || reactRootContainer === undefined) &&
parentNode.onclick === null
) {
// TODO: This cast may not be sound for SVG, MathML or custom elements.trapClickOnNonInteractiveElement(parentNode); }}Copy the code
6.3 commitLayoutEffects
function commitLayoutEffects(root, committedLanes) {
// TODO: Should probably move the bulk of this function to commitWork.
while(nextEffect ! = =null) {
const effectTag = nextEffect.effectTag;
if (effectTag & (Update | Callback)) {
const current = nextEffect.alternate;
commitLayoutEffectOnFiber(root, current, nextEffect, committedLanes);
}
if(effectTag & Ref) { commitAttachRef(nextEffect); } nextEffect = nextEffect.nextEffect; }}function commitLayoutEffectOnFiber(finishedRoot, current, finishedWork, committedLanes) {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent:
case Block: {
// At this point layout effects have already been destroyed (during mutation phase).
// This is done to prevent sibling component effects from interfering with each other,
// e.g. a destroy function in one component should never override a ref set
// by a create function in another component during the same commit.
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
finishedWork.mode & ProfileMode
) {
try {
startLayoutEffectTimer();
commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
} finally{ recordLayoutEffectDuration(finishedWork); }}else {
commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
}
if (runAllPassiveEffectDestroysBeforeCreates) {
schedulePassiveEffects(finishedWork);
}
return;
}
case ClassComponent: {
const instance = finishedWork.stateNode;
if (finishedWork.effectTag & Update) {
if (current === null) {
if (__DEV__) {
// ...
}
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
finishedWork.mode & ProfileMode
) {
try {
startLayoutEffectTimer();
instance.componentDidMount();
} finally{ recordLayoutEffectDuration(finishedWork); }}else{ instance.componentDidMount(); }}else {
const prevProps =
finishedWork.elementType === finishedWork.type
? current.memoizedProps
: resolveDefaultProps(finishedWork.type, current.memoizedProps);
const prevState = current.memoizedState;
// We could update instance props and state here,
// but instead we rely on them being set during last render.
// TODO: revisit this when we implement resuming.
if (__DEV__) {
// ...
}
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
finishedWork.mode & ProfileMode
) {
try {
startLayoutEffectTimer();
instance.componentDidUpdate(
prevProps,
prevState,
instance.__reactInternalSnapshotBeforeUpdate
);
} finally{ recordLayoutEffectDuration(finishedWork); }}else {
// true
// Life Cycle four (componentDidUpdate) : The mount is completeinstance.componentDidUpdate( prevProps, prevState, instance.__reactInternalSnapshotBeforeUpdate ); }}}// TODO: I think this is now always non-null by the time it reaches the
// commit phase. Consider removing the type check.
const updateQueue = finishedWork.updateQueue;
if(updateQueue ! = =null) {
if (__DEV__) {
// ...
}
// We could update instance props and state here,
// but instead we rely on them being set during last render.
// TODO: revisit this when we implement resuming.
commitUpdateQueue(finishedWork, updateQueue, instance);
}
return;
}
case HostRoot: {
// TODO: I think this is now always non-null by the time it reaches the
// commit phase. Consider removing the type check.
const updateQueue = finishedWork.updateQueue;
if(updateQueue ! = =null) {
let instance = null;
if(finishedWork.child ! = =null) {
switch (finishedWork.child.tag) {
case HostComponent:
instance = getPublicInstance(finishedWork.child.stateNode);
break;
case ClassComponent:
instance = finishedWork.child.stateNode;
break;
}
}
commitUpdateQueue(finishedWork, updateQueue, instance);
}
return;
}
case HostComponent: {
const instance = finishedWork.stateNode;
// Renderers may schedule work to be done after host components are mounted
// (eg DOM renderer may schedule auto-focus for inputs and form controls).
// These effects should only be committed when components are first mounted,
// aka when there is no current/alternate.
if (current === null && finishedWork.effectTag & Update) {
const type = finishedWork.type;
const props = finishedWork.memoizedProps;
commitMount(instance, type, props, finishedWork);
}
return;
}
case HostText: {
// We have no life-cycles associated with text.
return;
}
case HostPortal: {
// We have no life-cycles associated with portals.
return;
}
case Profiler: {
if (enableProfilerTimer) {
const { onCommit, onRender } = finishedWork.memoizedProps;
const { effectDuration } = finishedWork.stateNode;
const commitTime = getCommitTime();
if (typeof onRender === "function") {
if (enableSchedulerTracing) {
onRender(
finishedWork.memoizedProps.id,
current === null ? "mount" : "update",
finishedWork.actualDuration,
finishedWork.treeBaseDuration,
finishedWork.actualStartTime,
commitTime,
finishedRoot.memoizedInteractions
);
} else {
onRender(
finishedWork.memoizedProps.id,
current === null ? "mount" : "update", finishedWork.actualDuration, finishedWork.treeBaseDuration, finishedWork.actualStartTime, commitTime ); }}if (enableProfilerCommitHooks) {
if (typeof onCommit === "function") {
if (enableSchedulerTracing) {
onCommit(
finishedWork.memoizedProps.id,
current === null ? "mount" : "update",
effectDuration,
commitTime,
finishedRoot.memoizedInteractions
);
} else {
onCommit(
finishedWork.memoizedProps.id,
current === null ? "mount" : "update", effectDuration, commitTime ); }}// Schedule a passive effect for this Profiler to call onPostCommit hooks.
// This effect should be scheduled even if there is no onPostCommit callback for this Profiler,
// because the effect is also where times bubble to parent Profilers.
enqueuePendingPassiveProfilerEffect(finishedWork);
// Propagate layout effect durations to the next nearest Profiler ancestor.
// Do not reset these values until the next render so DevTools has a chance to read them first.
let parentFiber = finishedWork.return;
while(parentFiber ! = =null) {
if (parentFiber.tag === Profiler) {
const parentStateNode = parentFiber.stateNode;
parentStateNode.effectDuration += effectDuration;
break; } parentFiber = parentFiber.return; }}}return;
}
case SuspenseComponent: {
commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
return;
}
case SuspenseListComponent:
case IncompleteClassComponent:
case FundamentalComponent:
case ScopeComponent:
case OffscreenComponent:
case LegacyHiddenComponent:
return;
}
invariant(
false."This unit of work tag should not have side-effects. This error is " +
"likely caused by a bug in React. Please file an issue."
);
}
Copy the code
7 Context
Context Api 相关。不深入了解 具体使用方法,请看context
/ / index
let index = - 1;
/ / value stack
const valueStack = [];
let fiberStack
if (__DEV__) {
fiberStack = [];
}
// Create a cursor data structure
function createCursor(defaultValue) {
return {
current: defaultValue,
};
}
function push(cursor, value, fiber) {
index++;
// Save the current value
valueStack[index] = cursor.current;
if (__DEV__) {
fiberStack[index] = fiber;
}
// Change the current cursor value
cursor.current = value;
}
function pop(cursor, fiber) {
if (index < 0) {
return;
}
// Restore the last saved value
cursor.current = valueStack[index];
// Delete data
valueStack[index] = null;
if (__DEV__) {
fiberStack[index] = null;
}
index--;
}
onst NO_CONTEXT = {};
const contextStackCursor = createCursor(NO_CONTEXT);
const contextFiberStackCursor = createCursor(NO_CONTEXT);
const rootInstanceStackCursor = createCursor(NO_CONTEXT);
const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
const MATH_NAMESPACE = "http://www.w3.org/1998/Math/MathML";
const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
function pushHostRootContext(workInProgress) {
function getIntrinsicNamespace(type) {
switch (type) {
case "svg":
return SVG_NAMESPACE;
case "math":
return MATH_NAMESPACE;
default:
returnHTML_NAMESPACE; }}function getChildNamespace(parentNamespace, type) {
if (parentNamespace == null || parentNamespace === HTML_NAMESPACE) {
// No (or default) parent namespace: potential entry point.
return getIntrinsicNamespace(type);
}
if (parentNamespace === SVG_NAMESPACE && type === "foreignObject") {
// We're leaving SVG.
return HTML_NAMESPACE;
}
// By default, pass namespace below.
return parentNamespace;
}
function getRootHostContext(rootContainerInstance) {
let type;
let namespace;
const nodeType = rootContainerInstance.nodeType;
switch (nodeType) {
case DOCUMENT_NODE:
case DOCUMENT_FRAGMENT_NODE: {
type = nodeType === DOCUMENT_NODE ? "#document" : "#fragment";
const root = rootContainerInstance.documentElement;
namespace = root ? root.namespaceURI : getChildNamespace(null."");
break;
}
default: {
const container =
nodeType === COMMENT_NODE
? rootContainerInstance.parentNode
: rootContainerInstance;
const ownNamespace = container.namespaceURI || null;
type = container.tagName;
namespace = getChildNamespace(ownNamespace, type);
break; }}return namespace;
}
function pushHostContainer(fiber, nextRootInstance) {
// Push the current root node instance onto the stack
// This allows us to reset the root node when the portals exit
push(rootInstanceStackCursor, nextRootInstance, fiber);
// Track the context and the Fiber that provided it.
// This enables us to pop only Fibers that provide unique contexts.
push(contextFiberStackCursor, fiber, fiber);
// Finally we need to push the host context onto the stack
// However, we can't just call the getRootHostContext method and push it
Because we have a different number
// Finally, we need to push the host context to the stack.
// However, we can't just call getRootHostContext() and push it because
// we'd have a different number of entries on the stack depending on
// whether getRootHostContext() throws somewhere in renderer code or not.
// So we push an empty value first. This lets us safely unwind on errors.
push(contextStackCursor, NO_CONTEXT, fiber);
const nextRootContext = getRootHostContext(nextRootInstance);
// Now that we know this function doesn't throw, replace it.
pop(contextStackCursor, fiber);
push(contextStackCursor, nextRootContext, fiber);
}
const root = workInProgress.stateNode;
pushHostContainer(workInProgress, root.containerInfo);
}
Copy the code
8 Follow-up Information:
- React16 diff function source code analysis
- How does scheduler work, the synchronous asynchrony issue of rendering
- Implementation principle of hooks