Skip to content

Commit a40c33e

Browse files
committed
Handle in Fiber
We match new Fibers regardless of what their key is if the current key is optimistic. This means that the new Fiber might have a different key for the first time so we need to handle updating it and restoring it properly. For the complex cases of maps, I use a negative index to represent previous optimistic keys so that we can look up to see if we match one of those.
1 parent 796399e commit a40c33e

File tree

2 files changed

+108
-20
lines changed

2 files changed

+108
-20
lines changed

packages/react-reconciler/src/ReactChildFiber.js

Lines changed: 94 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import type {
1616
ReactComponentInfo,
1717
SuspenseListRevealOrder,
1818
ReactKey,
19+
ReactOptimisticKey,
1920
} from 'shared/ReactTypes';
2021
import type {Fiber} from './ReactInternalTypes';
2122
import type {Lanes} from './ReactFiberLane';
@@ -38,6 +39,7 @@ import {
3839
REACT_LAZY_TYPE,
3940
REACT_CONTEXT_TYPE,
4041
REACT_LEGACY_ELEMENT_TYPE,
42+
REACT_OPTIMISTIC_KEY,
4143
} from 'shared/ReactSymbols';
4244
import {
4345
HostRoot,
@@ -51,6 +53,7 @@ import {
5153
enableAsyncIterableChildren,
5254
disableLegacyMode,
5355
enableFragmentRefs,
56+
enableOptimisticKey,
5457
} from 'shared/ReactFeatureFlags';
5558

5659
import {
@@ -463,18 +466,33 @@ function createChildReconciler(
463466

464467
function mapRemainingChildren(
465468
currentFirstChild: Fiber,
466-
): Map<string | number, Fiber> {
469+
): Map<string | number | ReactOptimisticKey, Fiber> {
467470
// Add the remaining children to a temporary map so that we can find them by
468471
// keys quickly. Implicit (null) keys get added to this set with their index
469472
// instead.
470-
const existingChildren: Map<string | number, Fiber> = new Map();
473+
const existingChildren: Map<
474+
| string
475+
| number
476+
// This type is only here for the case when enableOptimisticKey is disabled.
477+
// Remove it after it ships.
478+
| ReactOptimisticKey,
479+
Fiber,
480+
> = new Map();
471481

472482
let existingChild: null | Fiber = currentFirstChild;
473483
while (existingChild !== null) {
474-
if (existingChild.key !== null) {
475-
existingChildren.set(existingChild.key, existingChild);
476-
} else {
484+
if (existingChild.key === null) {
477485
existingChildren.set(existingChild.index, existingChild);
486+
} else if (
487+
enableOptimisticKey &&
488+
existingChild.key === REACT_OPTIMISTIC_KEY
489+
) {
490+
// For optimistic keys, we store the negative index (minus one) to differentiate
491+
// them from the regular indices. We'll look this up regardless of what the new
492+
// key is, if there's no other match.
493+
existingChildren.set(-existingChild.index - 1, existingChild);
494+
} else {
495+
existingChildren.set(existingChild.key, existingChild);
478496
}
479497
existingChild = existingChild.sibling;
480498
}
@@ -637,6 +655,10 @@ function createChildReconciler(
637655
} else {
638656
// Update
639657
const existing = useFiber(current, portal.children || []);
658+
if (enableOptimisticKey) {
659+
// If the old key was optimistic we need to now save the real one.
660+
existing.key = portal.key;
661+
}
640662
existing.return = returnFiber;
641663
if (__DEV__) {
642664
existing._debugInfo = currentDebugInfo;
@@ -671,6 +693,10 @@ function createChildReconciler(
671693
} else {
672694
// Update
673695
const existing = useFiber(current, fragment);
696+
if (enableOptimisticKey) {
697+
// If the old key was optimistic we need to now save the real one.
698+
existing.key = key;
699+
}
674700
existing.return = returnFiber;
675701
if (__DEV__) {
676702
existing._debugInfo = currentDebugInfo;
@@ -841,7 +867,10 @@ function createChildReconciler(
841867
if (typeof newChild === 'object' && newChild !== null) {
842868
switch (newChild.$$typeof) {
843869
case REACT_ELEMENT_TYPE: {
844-
if (newChild.key === key) {
870+
if (
871+
newChild.key === key ||
872+
(enableOptimisticKey && key === REACT_OPTIMISTIC_KEY)
873+
) {
845874
const prevDebugInfo = pushDebugInfo(newChild._debugInfo);
846875
const updated = updateElement(
847876
returnFiber,
@@ -856,7 +885,10 @@ function createChildReconciler(
856885
}
857886
}
858887
case REACT_PORTAL_TYPE: {
859-
if (newChild.key === key) {
888+
if (
889+
newChild.key === key ||
890+
(enableOptimisticKey && key === REACT_OPTIMISTIC_KEY)
891+
) {
860892
return updatePortal(returnFiber, oldFiber, newChild, lanes);
861893
} else {
862894
return null;
@@ -940,7 +972,7 @@ function createChildReconciler(
940972
}
941973

942974
function updateFromMap(
943-
existingChildren: Map<string | number, Fiber>,
975+
existingChildren: Map<string | number | ReactOptimisticKey, Fiber>,
944976
returnFiber: Fiber,
945977
newIdx: number,
946978
newChild: any,
@@ -969,7 +1001,11 @@ function createChildReconciler(
9691001
const matchedFiber =
9701002
existingChildren.get(
9711003
newChild.key === null ? newIdx : newChild.key,
972-
) || null;
1004+
) ||
1005+
(enableOptimisticKey &&
1006+
// If the existing child was an optimistic key, we may still match on the index.
1007+
existingChildren.get(-newIdx - 1)) ||
1008+
null;
9731009
const prevDebugInfo = pushDebugInfo(newChild._debugInfo);
9741010
const updated = updateElement(
9751011
returnFiber,
@@ -984,7 +1020,11 @@ function createChildReconciler(
9841020
const matchedFiber =
9851021
existingChildren.get(
9861022
newChild.key === null ? newIdx : newChild.key,
987-
) || null;
1023+
) ||
1024+
(enableOptimisticKey &&
1025+
// If the existing child was an optimistic key, we may still match on the index.
1026+
existingChildren.get(-newIdx - 1)) ||
1027+
null;
9881028
return updatePortal(returnFiber, matchedFiber, newChild, lanes);
9891029
}
9901030
case REACT_LAZY_TYPE: {
@@ -1275,14 +1315,22 @@ function createChildReconciler(
12751315
);
12761316
}
12771317
if (shouldTrackSideEffects) {
1278-
if (newFiber.alternate !== null) {
1318+
const currentFiber = newFiber.alternate;
1319+
if (currentFiber !== null) {
12791320
// The new fiber is a work in progress, but if there exists a
12801321
// current, that means that we reused the fiber. We need to delete
12811322
// it from the child list so that we don't add it to the deletion
12821323
// list.
1283-
existingChildren.delete(
1284-
newFiber.key === null ? newIdx : newFiber.key,
1285-
);
1324+
if (
1325+
enableOptimisticKey &&
1326+
currentFiber.key === REACT_OPTIMISTIC_KEY
1327+
) {
1328+
existingChildren.delete(-newIdx - 1);
1329+
} else {
1330+
existingChildren.delete(
1331+
currentFiber.key === null ? newIdx : currentFiber.key,
1332+
);
1333+
}
12861334
}
12871335
}
12881336
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
@@ -1569,14 +1617,22 @@ function createChildReconciler(
15691617
);
15701618
}
15711619
if (shouldTrackSideEffects) {
1572-
if (newFiber.alternate !== null) {
1620+
const currentFiber = newFiber.alternate;
1621+
if (currentFiber !== null) {
15731622
// The new fiber is a work in progress, but if there exists a
15741623
// current, that means that we reused the fiber. We need to delete
15751624
// it from the child list so that we don't add it to the deletion
15761625
// list.
1577-
existingChildren.delete(
1578-
newFiber.key === null ? newIdx : newFiber.key,
1579-
);
1626+
if (
1627+
enableOptimisticKey &&
1628+
currentFiber.key === REACT_OPTIMISTIC_KEY
1629+
) {
1630+
existingChildren.delete(-newIdx - 1);
1631+
} else {
1632+
existingChildren.delete(
1633+
currentFiber.key === null ? newIdx : currentFiber.key,
1634+
);
1635+
}
15801636
}
15811637
}
15821638
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
@@ -1643,12 +1699,19 @@ function createChildReconciler(
16431699
while (child !== null) {
16441700
// TODO: If key === null and child.key === null, then this only applies to
16451701
// the first item in the list.
1646-
if (child.key === key) {
1702+
if (
1703+
child.key === key ||
1704+
(enableOptimisticKey && child.key === REACT_OPTIMISTIC_KEY)
1705+
) {
16471706
const elementType = element.type;
16481707
if (elementType === REACT_FRAGMENT_TYPE) {
16491708
if (child.tag === Fragment) {
16501709
deleteRemainingChildren(returnFiber, child.sibling);
16511710
const existing = useFiber(child, element.props.children);
1711+
if (enableOptimisticKey) {
1712+
// If the old key was optimistic we need to now save the real one.
1713+
existing.key = key;
1714+
}
16521715
if (enableFragmentRefs) {
16531716
coerceRef(existing, element);
16541717
}
@@ -1678,6 +1741,10 @@ function createChildReconciler(
16781741
) {
16791742
deleteRemainingChildren(returnFiber, child.sibling);
16801743
const existing = useFiber(child, element.props);
1744+
if (enableOptimisticKey) {
1745+
// If the old key was optimistic we need to now save the real one.
1746+
existing.key = key;
1747+
}
16811748
coerceRef(existing, element);
16821749
existing.return = returnFiber;
16831750
if (__DEV__) {
@@ -1737,14 +1804,21 @@ function createChildReconciler(
17371804
while (child !== null) {
17381805
// TODO: If key === null and child.key === null, then this only applies to
17391806
// the first item in the list.
1740-
if (child.key === key) {
1807+
if (
1808+
child.key === key ||
1809+
(enableOptimisticKey && child.key === REACT_OPTIMISTIC_KEY)
1810+
) {
17411811
if (
17421812
child.tag === HostPortal &&
17431813
child.stateNode.containerInfo === portal.containerInfo &&
17441814
child.stateNode.implementation === portal.implementation
17451815
) {
17461816
deleteRemainingChildren(returnFiber, child.sibling);
17471817
const existing = useFiber(child, portal.children || []);
1818+
if (enableOptimisticKey) {
1819+
// If the old key was optimistic we need to now save the real one.
1820+
existing.key = key;
1821+
}
17481822
existing.return = returnFiber;
17491823
return existing;
17501824
} else {

packages/react-reconciler/src/ReactFiber.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import {
4444
enableObjectFiber,
4545
enableViewTransition,
4646
enableSuspenseyImages,
47+
enableOptimisticKey,
4748
} from 'shared/ReactFeatureFlags';
4849
import {NoFlags, Placement, StaticMask} from './ReactFiberFlags';
4950
import {ConcurrentRoot} from './ReactRootTags';
@@ -365,6 +366,12 @@ export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
365366
workInProgress.subtreeFlags = NoFlags;
366367
workInProgress.deletions = null;
367368

369+
if (enableOptimisticKey) {
370+
// For optimistic keys, the Fibers can have different keys if one is optimistic
371+
// and the other one is filled in.
372+
workInProgress.key = current.key;
373+
}
374+
368375
if (enableProfilerTimer) {
369376
// We intentionally reset, rather than copy, actualDuration & actualStartTime.
370377
// This prevents time from endlessly accumulating in new commits.
@@ -489,8 +496,15 @@ export function resetWorkInProgress(
489496
workInProgress.memoizedState = current.memoizedState;
490497
workInProgress.updateQueue = current.updateQueue;
491498
// Needed because Blocks store data on type.
499+
// TODO: Blocks don't exist anymore. Do we still need this?
492500
workInProgress.type = current.type;
493501

502+
if (enableOptimisticKey) {
503+
// For optimistic keys, the Fibers can have different keys if one is optimistic
504+
// and the other one is filled in.
505+
workInProgress.key = current.key;
506+
}
507+
494508
// Clone the dependencies object. This is mutated during the render phase, so
495509
// it cannot be shared with the current fiber.
496510
const currentDependencies = current.dependencies;

0 commit comments

Comments
 (0)