@@ -35,6 +35,7 @@ import {
3535 enableSuspenseCallback ,
3636 enableScopeAPI ,
3737 enableStrictEffects ,
38+ deletedTreeCleanUpLevel ,
3839} from 'shared/ReactFeatureFlags' ;
3940import {
4041 FunctionComponent ,
@@ -60,6 +61,7 @@ import {
6061 hasCaughtError ,
6162 clearCaughtError ,
6263} from 'shared/ReactErrorUtils' ;
64+ import { detachDeletedInstance } from './ReactFiberHostConfig' ;
6365import {
6466 NoFlags ,
6567 ContentReset ,
@@ -1219,25 +1221,84 @@ function detachFiberMutation(fiber: Fiber) {
12191221 // Don't reset the alternate yet, either. We need that so we can detach the
12201222 // alternate's fields in the passive phase. Clearing the return pointer is
12211223 // sufficient for findDOMNode semantics.
1224+ const alternate = fiber . alternate ;
1225+ if ( alternate !== null ) {
1226+ alternate . return = null ;
1227+ }
12221228 fiber . return = null ;
12231229}
12241230
1225- export function detachFiberAfterEffects ( fiber : Fiber ) : void {
1226- // Null out fields to improve GC for references that may be lingering (e.g. DevTools).
1227- // Note that we already cleared the return pointer in detachFiberMutation().
1228- fiber . alternate = null ;
1229- fiber . child = null ;
1230- fiber . deletions = null ;
1231- fiber . dependencies = null ;
1232- fiber . memoizedProps = null ;
1233- fiber . memoizedState = null ;
1234- fiber . pendingProps = null ;
1235- fiber . sibling = null ;
1236- fiber . stateNode = null ;
1237- fiber . updateQueue = null ;
1231+ function detachFiberAfterEffects ( fiber : Fiber ) {
1232+ const alternate = fiber . alternate ;
1233+ if ( alternate !== null ) {
1234+ fiber . alternate = null ;
1235+ detachFiberAfterEffects ( alternate ) ;
1236+ }
1237+
1238+ // Note: Defensively using negation instead of < in case
1239+ // `deletedTreeCleanUpLevel` is undefined.
1240+ if ( ! ( deletedTreeCleanUpLevel >= 2 ) ) {
1241+ // This is the default branch (level 0).
1242+ fiber . child = null ;
1243+ fiber . deletions = null ;
1244+ fiber . dependencies = null ;
1245+ fiber . memoizedProps = null ;
1246+ fiber . memoizedState = null ;
1247+ fiber . pendingProps = null ;
1248+ fiber . sibling = null ;
1249+ fiber . stateNode = null ;
1250+ fiber . updateQueue = null ;
12381251
1239- if ( __DEV__ ) {
1240- fiber . _debugOwner = null ;
1252+ if ( __DEV__ ) {
1253+ fiber . _debugOwner = null ;
1254+ }
1255+ } else {
1256+ // Clear cyclical Fiber fields. This level alone is designed to roughly
1257+ // approximate the planned Fiber refactor. In that world, `setState` will be
1258+ // bound to a special "instance" object instead of a Fiber. The Instance
1259+ // object will not have any of these fields. It will only be connected to
1260+ // the fiber tree via a single link at the root. So if this level alone is
1261+ // sufficient to fix memory issues, that bodes well for our plans.
1262+ fiber . child = null ;
1263+ fiber . deletions = null ;
1264+ fiber . sibling = null ;
1265+
1266+ // I'm intentionally not clearing the `return` field in this level. We
1267+ // already disconnect the `return` pointer at the root of the deleted
1268+ // subtree (in `detachFiberMutation`). Besides, `return` by itself is not
1269+ // cyclical — it's only cyclical when combined with `child`, `sibling`, and
1270+ // `alternate`. But we'll clear it in the next level anyway, just in case.
1271+
1272+ if ( __DEV__ ) {
1273+ fiber . _debugOwner = null ;
1274+ }
1275+
1276+ if ( deletedTreeCleanUpLevel >= 3 ) {
1277+ // Theoretically, nothing in here should be necessary, because we already
1278+ // disconnected the fiber from the tree. So even if something leaks this
1279+ // particular fiber, it won't leak anything else
1280+ //
1281+ // The purpose of this branch is to be super aggressive so we can measure
1282+ // if there's any difference in memory impact. If there is, that could
1283+ // indicate a React leak we don't know about.
1284+
1285+ // For host components, disconnect host instance -> fiber pointer.
1286+ if ( fiber . tag === HostComponent ) {
1287+ const hostInstance : Instance = fiber . stateNode ;
1288+ if ( hostInstance !== null ) {
1289+ detachDeletedInstance ( hostInstance ) ;
1290+ }
1291+ }
1292+
1293+ fiber . return = null ;
1294+ fiber . dependencies = null ;
1295+ fiber . memoizedProps = null ;
1296+ fiber . memoizedState = null ;
1297+ fiber . pendingProps = null ;
1298+ fiber . stateNode = null ;
1299+ // TODO: Move to `commitPassiveUnmountInsideDeletedTreeOnFiber` instead.
1300+ fiber . updateQueue = null ;
1301+ }
12411302 }
12421303}
12431304
@@ -1629,11 +1690,8 @@ function commitDeletion(
16291690 renderPriorityLevel ,
16301691 ) ;
16311692 }
1632- const alternate = current . alternate ;
1693+
16331694 detachFiberMutation ( current ) ;
1634- if ( alternate !== null ) {
1635- detachFiberMutation ( alternate ) ;
1636- }
16371695}
16381696
16391697function commitWork ( current : Fiber | null , finishedWork : Fiber ) : void {
@@ -2308,14 +2366,34 @@ function commitPassiveUnmountEffects_begin() {
23082366 fiberToDelete ,
23092367 fiber ,
23102368 ) ;
2369+ }
23112370
2312- // Now that passive effects have been processed, it's safe to detach lingering pointers.
2313- const alternate = fiberToDelete . alternate ;
2314- detachFiberAfterEffects ( fiberToDelete ) ;
2315- if ( alternate !== null ) {
2316- detachFiberAfterEffects ( alternate ) ;
2371+ if ( deletedTreeCleanUpLevel >= 1 ) {
2372+ // A fiber was deleted from this parent fiber, but it's still part of
2373+ // the previous (alternate) parent fiber's list of children. Because
2374+ // children are a linked list, an earlier sibling that's still alive
2375+ // will be connected to the deleted fiber via its `alternate`:
2376+ //
2377+ // live fiber
2378+ // --alternate--> previous live fiber
2379+ // --sibling--> deleted fiber
2380+ //
2381+ // We can't disconnect `alternate` on nodes that haven't been deleted
2382+ // yet, but we can disconnect the `sibling` and `child` pointers.
2383+ const previousFiber = fiber . alternate ;
2384+ if ( previousFiber !== null ) {
2385+ let detachedChild = previousFiber . child ;
2386+ if ( detachedChild !== null ) {
2387+ previousFiber . child = null ;
2388+ do {
2389+ const detachedSibling = detachedChild . sibling ;
2390+ detachedChild . sibling = null ;
2391+ detachedChild = detachedSibling ;
2392+ } while ( detachedChild !== null ) ;
2393+ }
23172394 }
23182395 }
2396+
23192397 nextEffect = fiber ;
23202398 }
23212399 }
@@ -2392,7 +2470,8 @@ function commitPassiveUnmountEffectsInsideOfDeletedTree_begin(
23922470 resetCurrentDebugFiberInDEV ( ) ;
23932471
23942472 const child = fiber . child ;
2395- // TODO: Only traverse subtree if it has a PassiveStatic flag
2473+ // TODO: Only traverse subtree if it has a PassiveStatic flag. (But, if we
2474+ // do this, still need to handle `deletedTreeCleanUpLevel` correctly.)
23962475 if ( child !== null ) {
23972476 ensureCorrectReturnPointer ( child , fiber ) ;
23982477 nextEffect = child ;
@@ -2409,19 +2488,35 @@ function commitPassiveUnmountEffectsInsideOfDeletedTree_complete(
24092488) {
24102489 while ( nextEffect !== null ) {
24112490 const fiber = nextEffect ;
2412- if ( fiber === deletedSubtreeRoot ) {
2413- nextEffect = null ;
2414- return ;
2491+ const sibling = fiber . sibling ;
2492+ const returnFiber = fiber . return ;
2493+
2494+ if ( deletedTreeCleanUpLevel >= 2 ) {
2495+ // Recursively traverse the entire deleted tree and clean up fiber fields.
2496+ // This is more aggressive than ideal, and the long term goal is to only
2497+ // have to detach the deleted tree at the root.
2498+ detachFiberAfterEffects ( fiber ) ;
2499+ if ( fiber === deletedSubtreeRoot ) {
2500+ nextEffect = null ;
2501+ return ;
2502+ }
2503+ } else {
2504+ // This is the default branch (level 0). We do not recursively clear all
2505+ // the fiber fields. Only the root of the deleted subtree.
2506+ if ( fiber === deletedSubtreeRoot ) {
2507+ detachFiberAfterEffects ( fiber ) ;
2508+ nextEffect = null ;
2509+ return ;
2510+ }
24152511 }
24162512
2417- const sibling = fiber . sibling ;
24182513 if ( sibling !== null ) {
2419- ensureCorrectReturnPointer ( sibling , fiber . return ) ;
2514+ ensureCorrectReturnPointer ( sibling , returnFiber ) ;
24202515 nextEffect = sibling ;
24212516 return ;
24222517 }
24232518
2424- nextEffect = fiber . return ;
2519+ nextEffect = returnFiber ;
24252520 }
24262521}
24272522
0 commit comments