Skip to content

Commit 3e78bf3

Browse files
committed
Defer more field detachments to passive phase
This allows us to use those fields during passive unmount traversal.
1 parent ca2c2ce commit 3e78bf3

File tree

2 files changed

+40
-23
lines changed

2 files changed

+40
-23
lines changed

packages/react-reconciler/src/ReactFiberCommitWork.new.js

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1069,30 +1069,42 @@ function commitNestedUnmounts(
10691069
}
10701070

10711071
function detachFiberMutation(fiber: Fiber) {
1072-
// Cut off the return pointers to disconnect it from the tree. Ideally, we
1073-
// should clear the child pointer of the parent alternate to let this
1072+
// Cut off the return pointer to disconnect it from the tree.
1073+
// This enables us to detect and warn against state updates on an unmounted component.
1074+
// It also prevents events from bubbling from within disconnected components.
1075+
//
1076+
// Ideally, we should also clear the child pointer of the parent alternate to let this
10741077
// get GC:ed but we don't know which for sure which parent is the current
1075-
// one so we'll settle for GC:ing the subtree of this child. This child
1076-
// itself will be GC:ed when the parent updates the next time.
1077-
// Note: we cannot null out sibling here, otherwise it can cause issues
1078-
// with findDOMNode and how it requires the sibling field to carry out
1079-
// traversal in a later effect. See PR #16820. We now clear the sibling
1080-
// field after effects, see: detachFiberAfterEffects.
1078+
// one so we'll settle for GC:ing the subtree of this child.
1079+
// This child itself will be GC:ed when the parent updates the next time.
10811080
//
1082-
// Don't disconnect stateNode now; it will be detached in detachFiberAfterEffects.
1083-
// It may be required if the current component is an error boundary,
1084-
// and one of its descendants throws while unmounting a passive effect.
1085-
fiber.alternate = null;
1081+
// Note that we can't clear child or sibling pointers yet.
1082+
// They're needed for passive effects and for findDOMNode.
1083+
// We defer those fields, and all other cleanup, to the passive phase (see detachFiberAfterEffects).
1084+
const alternate = fiber.alternate;
1085+
if (alternate !== null) {
1086+
alternate.return = null;
1087+
fiber.alternate = null;
1088+
}
1089+
fiber.return = null;
1090+
}
1091+
1092+
export function detachFiberAfterEffects(fiber: Fiber): void {
1093+
// Null out fields to improve GC for references that may be lingering (e.g. DevTools).
1094+
// Note that we already cleared the return pointer in detachFiberMutation().
10861095
fiber.child = null;
10871096
fiber.deletions = null;
10881097
fiber.dependencies = null;
1089-
fiber.firstEffect = null;
1090-
fiber.lastEffect = null;
10911098
fiber.memoizedProps = null;
10921099
fiber.memoizedState = null;
10931100
fiber.pendingProps = null;
1094-
fiber.return = null;
1101+
fiber.sibling = null;
1102+
fiber.stateNode = null;
10951103
fiber.updateQueue = null;
1104+
fiber.nextEffect = null;
1105+
fiber.firstEffect = null;
1106+
fiber.lastEffect = null;
1107+
10961108
if (__DEV__) {
10971109
fiber._debugOwner = null;
10981110
}

packages/react-reconciler/src/ReactFiberWorkLoop.new.js

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ import {
121121
Update,
122122
PlacementAndUpdate,
123123
Deletion,
124+
ChildDeletion,
124125
Ref,
125126
ContentReset,
126127
Snapshot,
@@ -193,6 +194,7 @@ import {
193194
commitResetTextContent,
194195
isSuspenseBoundaryBeingHidden,
195196
commitPassiveMountEffects,
197+
detachFiberAfterEffects,
196198
} from './ReactFiberCommitWork.new';
197199
import {enqueueUpdate} from './ReactUpdateQueue.new';
198200
import {resetContextDependencies} from './ReactFiberNewContext.new';
@@ -2116,13 +2118,21 @@ function commitRootImpl(root, renderPriorityLevel) {
21162118
} else {
21172119
// We are done with the effect chain at this point so let's clear the
21182120
// nextEffect pointers to assist with GC. If we have passive effects, we'll
2119-
// clear this in flushPassiveEffects.
2121+
// clear this in flushPassiveEffects
2122+
// TODO: We should always do this in the passive phase, by scheduling
2123+
// a passive callback for every deletion.
21202124
nextEffect = firstEffect;
21212125
while (nextEffect !== null) {
21222126
const nextNextEffect = nextEffect.nextEffect;
21232127
nextEffect.nextEffect = null;
2124-
if (nextEffect.flags & Deletion) {
2125-
detachFiberAfterEffects(nextEffect);
2128+
if (nextEffect.flags & ChildDeletion) {
2129+
const deletions = nextEffect.deletions;
2130+
if (deletions !== null) {
2131+
for (let i = 0; i < deletions.length; i++) {
2132+
const deletion = deletions[i];
2133+
detachFiberAfterEffects(deletion);
2134+
}
2135+
}
21262136
}
21272137
nextEffect = nextNextEffect;
21282138
}
@@ -3687,8 +3697,3 @@ export function act(callback: () => Thenable<mixed>): Thenable<void> {
36873697
};
36883698
}
36893699
}
3690-
3691-
function detachFiberAfterEffects(fiber: Fiber): void {
3692-
fiber.sibling = null;
3693-
fiber.stateNode = null;
3694-
}

0 commit comments

Comments
 (0)