mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
DevTools should iterate over siblings during mount (#21377)
Previously, DevTools recursed over both children and siblings during mount. This caused potential stack overflows when there were a lot of children (e.g. a list containing many items).
Given the following example component tree:
A
B C D
E F
G
A method that recurses for every child and sibling leads to a max depth of 6:
A
A -> B
A -> B -> E
A -> B -> C
A -> B -> C -> D
A -> B -> C -> D -> F
A -> B -> C -> D -> F -> G
The stack gets deeper as the tree gets either deeper or wider.
A method that recurses for every child and iterates over siblings leads to a max depth of 4:
A
A -> B
A -> B -> E
A -> C
A -> D
A -> D -> F
A -> D -> F -> G
The stack gets deeper as the tree gets deeper but is resilient to wide trees (e.g. lists containing many items).
This commit is contained in:
+76
-78
@@ -1647,102 +1647,100 @@ export function attach(
|
||||
}
|
||||
|
||||
function mountFiberRecursively(
|
||||
fiber: Fiber,
|
||||
firstChild: Fiber,
|
||||
parentFiber: Fiber | null,
|
||||
traverseSiblings: boolean,
|
||||
traceNearestHostComponentUpdate: boolean,
|
||||
) {
|
||||
if (__DEBUG__) {
|
||||
debug('mountFiberRecursively()', fiber, parentFiber);
|
||||
}
|
||||
|
||||
// If we have the tree selection from previous reload, try to match this Fiber.
|
||||
// Also remember whether to do the same for siblings.
|
||||
const mightSiblingsBeOnTrackedPath = updateTrackedPathStateBeforeMount(
|
||||
fiber,
|
||||
);
|
||||
|
||||
const shouldIncludeInTree = !shouldFilterFiber(fiber);
|
||||
if (shouldIncludeInTree) {
|
||||
recordMount(fiber, parentFiber);
|
||||
}
|
||||
|
||||
if (traceUpdatesEnabled) {
|
||||
if (traceNearestHostComponentUpdate) {
|
||||
const elementType = getElementTypeForFiber(fiber);
|
||||
// If an ancestor updated, we should mark the nearest host nodes for highlighting.
|
||||
if (elementType === ElementTypeHostComponent) {
|
||||
traceUpdatesForNodes.add(fiber.stateNode);
|
||||
traceNearestHostComponentUpdate = false;
|
||||
}
|
||||
// Iterate over siblings rather than recursing.
|
||||
// This reduces the chance of stack overflow for wide trees (e.g. lists with many items).
|
||||
let fiber: Fiber | null = firstChild;
|
||||
while (fiber !== null) {
|
||||
if (__DEBUG__) {
|
||||
debug('mountFiberRecursively()', fiber, parentFiber);
|
||||
}
|
||||
|
||||
// We intentionally do not re-enable the traceNearestHostComponentUpdate flag in this branch,
|
||||
// because we don't want to highlight every host node inside of a newly mounted subtree.
|
||||
}
|
||||
// If we have the tree selection from previous reload, try to match this Fiber.
|
||||
// Also remember whether to do the same for siblings.
|
||||
const mightSiblingsBeOnTrackedPath = updateTrackedPathStateBeforeMount(
|
||||
fiber,
|
||||
);
|
||||
|
||||
const isSuspense = fiber.tag === ReactTypeOfWork.SuspenseComponent;
|
||||
if (isSuspense) {
|
||||
const isTimedOut = fiber.memoizedState !== null;
|
||||
if (isTimedOut) {
|
||||
// Special case: if Suspense mounts in a timed-out state,
|
||||
// get the fallback child from the inner fragment and mount
|
||||
// it as if it was our own child. Updates handle this too.
|
||||
const primaryChildFragment = fiber.child;
|
||||
const fallbackChildFragment = primaryChildFragment
|
||||
? primaryChildFragment.sibling
|
||||
: null;
|
||||
const fallbackChild = fallbackChildFragment
|
||||
? fallbackChildFragment.child
|
||||
: null;
|
||||
if (fallbackChild !== null) {
|
||||
mountFiberRecursively(
|
||||
fallbackChild,
|
||||
shouldIncludeInTree ? fiber : parentFiber,
|
||||
true,
|
||||
traceNearestHostComponentUpdate,
|
||||
);
|
||||
const shouldIncludeInTree = !shouldFilterFiber(fiber);
|
||||
if (shouldIncludeInTree) {
|
||||
recordMount(fiber, parentFiber);
|
||||
}
|
||||
|
||||
if (traceUpdatesEnabled) {
|
||||
if (traceNearestHostComponentUpdate) {
|
||||
const elementType = getElementTypeForFiber(fiber);
|
||||
// If an ancestor updated, we should mark the nearest host nodes for highlighting.
|
||||
if (elementType === ElementTypeHostComponent) {
|
||||
traceUpdatesForNodes.add(fiber.stateNode);
|
||||
traceNearestHostComponentUpdate = false;
|
||||
}
|
||||
}
|
||||
|
||||
// We intentionally do not re-enable the traceNearestHostComponentUpdate flag in this branch,
|
||||
// because we don't want to highlight every host node inside of a newly mounted subtree.
|
||||
}
|
||||
|
||||
const isSuspense = fiber.tag === ReactTypeOfWork.SuspenseComponent;
|
||||
if (isSuspense) {
|
||||
const isTimedOut = fiber.memoizedState !== null;
|
||||
if (isTimedOut) {
|
||||
// Special case: if Suspense mounts in a timed-out state,
|
||||
// get the fallback child from the inner fragment and mount
|
||||
// it as if it was our own child. Updates handle this too.
|
||||
const primaryChildFragment = fiber.child;
|
||||
const fallbackChildFragment = primaryChildFragment
|
||||
? primaryChildFragment.sibling
|
||||
: null;
|
||||
const fallbackChild = fallbackChildFragment
|
||||
? fallbackChildFragment.child
|
||||
: null;
|
||||
if (fallbackChild !== null) {
|
||||
mountFiberRecursively(
|
||||
fallbackChild,
|
||||
shouldIncludeInTree ? fiber : parentFiber,
|
||||
true,
|
||||
traceNearestHostComponentUpdate,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
let primaryChild: Fiber | null = null;
|
||||
const areSuspenseChildrenConditionallyWrapped =
|
||||
OffscreenComponent === -1;
|
||||
if (areSuspenseChildrenConditionallyWrapped) {
|
||||
primaryChild = fiber.child;
|
||||
} else if (fiber.child !== null) {
|
||||
primaryChild = fiber.child.child;
|
||||
}
|
||||
if (primaryChild !== null) {
|
||||
mountFiberRecursively(
|
||||
primaryChild,
|
||||
shouldIncludeInTree ? fiber : parentFiber,
|
||||
true,
|
||||
traceNearestHostComponentUpdate,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let primaryChild: Fiber | null = null;
|
||||
const areSuspenseChildrenConditionallyWrapped =
|
||||
OffscreenComponent === -1;
|
||||
if (areSuspenseChildrenConditionallyWrapped) {
|
||||
primaryChild = fiber.child;
|
||||
} else if (fiber.child !== null) {
|
||||
primaryChild = fiber.child.child;
|
||||
}
|
||||
if (primaryChild !== null) {
|
||||
if (fiber.child !== null) {
|
||||
mountFiberRecursively(
|
||||
primaryChild,
|
||||
fiber.child,
|
||||
shouldIncludeInTree ? fiber : parentFiber,
|
||||
true,
|
||||
traceNearestHostComponentUpdate,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (fiber.child !== null) {
|
||||
mountFiberRecursively(
|
||||
fiber.child,
|
||||
shouldIncludeInTree ? fiber : parentFiber,
|
||||
true,
|
||||
traceNearestHostComponentUpdate,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// We're exiting this Fiber now, and entering its siblings.
|
||||
// If we have selection to restore, we might need to re-activate tracking.
|
||||
updateTrackedPathStateAfterMount(mightSiblingsBeOnTrackedPath);
|
||||
// We're exiting this Fiber now, and entering its siblings.
|
||||
// If we have selection to restore, we might need to re-activate tracking.
|
||||
updateTrackedPathStateAfterMount(mightSiblingsBeOnTrackedPath);
|
||||
|
||||
if (traverseSiblings && fiber.sibling !== null) {
|
||||
mountFiberRecursively(
|
||||
fiber.sibling,
|
||||
parentFiber,
|
||||
true,
|
||||
traceNearestHostComponentUpdate,
|
||||
);
|
||||
fiber = traverseSiblings ? fiber.sibling : null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user