35
enableSuspenseCallback,
36
enableScopeAPI,
37
enableStrictEffects,
38
+ deletedTreeCleanUpLevel,
39
} from 'shared/ReactFeatureFlags';
40
import {
41
FunctionComponent,
61
hasCaughtError,
62
clearCaughtError,
63
} from 'shared/ReactErrorUtils';
64
+import {detachDeletedInstance} from './ReactFiberHostConfig';
65
import {
66
NoFlags,
67
ContentReset,
1221
// Don't reset the alternate yet, either. We need that so we can detach the
1222
// alternate's fields in the passive phase. Clearing the return pointer is
1223
// sufficient for findDOMNode semantics.
1224
+ const alternate = fiber.alternate;
1225
+ if (alternate !== null) {
1226
+ alternate.return = null;
1227
+ }
1228
fiber.return = null;
1229
}
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;
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
+ }
1302
}
1303
}
1304
1690
renderPriorityLevel,
1691
);
1692
}
1632
- const alternate = current.alternate;
1693
+
1694
detachFiberMutation(current);
1634
- if (alternate !== null) {
1635
- detachFiberMutation(alternate);
1636
- }
1695
}
1696
1697
function commitWork(current: Fiber | null, finishedWork: Fiber): void {
2366
fiberToDelete,
2367
fiber,
2368
);
2369
+ }
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
+ }
2394
}
2395
}
2396
+
2397
nextEffect = fiber;
2398
}
2399
}
2470
resetCurrentDebugFiberInDEV();
2471
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.)
2475
if (child !== null) {
2476
ensureCorrectReturnPointer(child, fiber);
2477
nextEffect = child;
2488
) {
2489
while (nextEffect !== null) {
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
+ }
2511
}
2512
2417
- const sibling = fiber.sibling;
2513
if (sibling !== null) {
2419
- ensureCorrectReturnPointer(sibling, fiber.return);
2514
+ ensureCorrectReturnPointer(sibling, returnFiber);
2515
nextEffect = sibling;
2516
return;
2517
}
2518
2424
- nextEffect = fiber.return;
2519
+ nextEffect = returnFiber;
2520
}
2521
}
2522