@@ -35,6 +35,7 @@ import {
35
35
enableSuspenseCallback ,
36
36
enableScopeAPI ,
37
37
enableStrictEffects ,
38
+ deletedTreeCleanUpLevel ,
38
39
} from 'shared/ReactFeatureFlags' ;
39
40
import {
40
41
FunctionComponent ,
@@ -60,6 +61,7 @@ import {
60
61
hasCaughtError ,
61
62
clearCaughtError ,
62
63
} from 'shared/ReactErrorUtils' ;
64
+ import { detachDeletedInstance } from './ReactFiberHostConfig' ;
63
65
import {
64
66
NoFlags ,
65
67
ContentReset ,
@@ -1219,25 +1221,84 @@ function detachFiberMutation(fiber: Fiber) {
1219
1221
// Don't reset the alternate yet, either. We need that so we can detach the
1220
1222
// alternate's fields in the passive phase. Clearing the return pointer is
1221
1223
// sufficient for findDOMNode semantics.
1224
+ const alternate = fiber . alternate ;
1225
+ if ( alternate !== null ) {
1226
+ alternate . return = null ;
1227
+ }
1222
1228
fiber . return = null ;
1223
1229
}
1224
1230
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 ;
1238
1251
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
+ }
1241
1302
}
1242
1303
}
1243
1304
@@ -1629,11 +1690,8 @@ function commitDeletion(
1629
1690
renderPriorityLevel ,
1630
1691
) ;
1631
1692
}
1632
- const alternate = current . alternate ;
1693
+
1633
1694
detachFiberMutation ( current ) ;
1634
- if ( alternate !== null ) {
1635
- detachFiberMutation ( alternate ) ;
1636
- }
1637
1695
}
1638
1696
1639
1697
function commitWork ( current : Fiber | null , finishedWork : Fiber ) : void {
@@ -2308,14 +2366,34 @@ function commitPassiveUnmountEffects_begin() {
2308
2366
fiberToDelete ,
2309
2367
fiber ,
2310
2368
) ;
2369
+ }
2311
2370
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
+ }
2317
2394
}
2318
2395
}
2396
+
2319
2397
nextEffect = fiber ;
2320
2398
}
2321
2399
}
@@ -2392,7 +2470,8 @@ function commitPassiveUnmountEffectsInsideOfDeletedTree_begin(
2392
2470
resetCurrentDebugFiberInDEV ( ) ;
2393
2471
2394
2472
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.)
2396
2475
if ( child !== null ) {
2397
2476
ensureCorrectReturnPointer ( child , fiber ) ;
2398
2477
nextEffect = child ;
@@ -2409,19 +2488,35 @@ function commitPassiveUnmountEffectsInsideOfDeletedTree_complete(
2409
2488
) {
2410
2489
while ( nextEffect !== null ) {
2411
2490
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
+ }
2415
2511
}
2416
2512
2417
- const sibling = fiber . sibling ;
2418
2513
if ( sibling !== null ) {
2419
- ensureCorrectReturnPointer ( sibling , fiber . return ) ;
2514
+ ensureCorrectReturnPointer ( sibling , returnFiber ) ;
2420
2515
nextEffect = sibling ;
2421
2516
return ;
2422
2517
}
2423
2518
2424
- nextEffect = fiber . return ;
2519
+ nextEffect = returnFiber ;
2425
2520
}
2426
2521
}
2427
2522
0 commit comments