Skip to content

Commit 12a738f

Browse files
authored
[Transition Tracing] Add Support for Multiple Transitions on Root (#24732)
We can think of transitions on the root as a bunch of tracing markers. Therefore, we can map each transition to a map of pending suspense boundaries. When a transition's pending suspense boundary map is empty, we know that it's complete. This PR: * Combines the `pendingSuspenseBoundaries` and `transitions` into one `incompleteTransitions` object. This object is a map from a `transition` to a map of `pendingSuspenseBoundaries` * Refactored code to make it so that every transition has its own `pendingSuspenseBoundaries` map rather than sharing just one. * Moves the transition complete callback to the root. Alternatively, we can also keep a map of pendingSuspenseBoundaries to transitions on the Offscreen marker, but it's simpler to just call the transition complete callback on the root instead. We also only do this if there are transitions pending, so it shouldn't make too big of a difference
1 parent 72ebc70 commit 12a738f

10 files changed

+394
-198
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -717,6 +717,8 @@ export function createFiberFromOffscreen(
717717
fiber.lanes = lanes;
718718
const primaryChildInstance: OffscreenInstance = {
719719
isHidden: false,
720+
pendingMarkers: null,
721+
transitions: null,
720722
};
721723
fiber.stateNode = primaryChildInstance;
722724
return fiber;

packages/react-reconciler/src/ReactFiber.old.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -717,6 +717,8 @@ export function createFiberFromOffscreen(
717717
fiber.lanes = lanes;
718718
const primaryChildInstance: OffscreenInstance = {
719719
isHidden: false,
720+
pendingMarkers: null,
721+
transitions: null,
720722
};
721723
fiber.stateNode = primaryChildInstance;
722724
return fiber;

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

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -675,7 +675,6 @@ function updateOffscreenComponent(
675675
const nextState: OffscreenState = {
676676
baseLanes: NoLanes,
677677
cachePool: null,
678-
transitions: null,
679678
};
680679
workInProgress.memoizedState = nextState;
681680
if (enableCache) {
@@ -709,7 +708,6 @@ function updateOffscreenComponent(
709708
const nextState: OffscreenState = {
710709
baseLanes: nextBaseLanes,
711710
cachePool: spawnedCachePool,
712-
transitions: null,
713711
};
714712
workInProgress.memoizedState = nextState;
715713
workInProgress.updateQueue = null;
@@ -745,7 +743,6 @@ function updateOffscreenComponent(
745743
const nextState: OffscreenState = {
746744
baseLanes: NoLanes,
747745
cachePool: null,
748-
transitions: null,
749746
};
750747
workInProgress.memoizedState = nextState;
751748
// Push the lanes that were skipped when we bailed out.
@@ -780,13 +777,10 @@ function updateOffscreenComponent(
780777
}
781778

782779
let transitions = null;
783-
if (
784-
workInProgress.memoizedState !== null &&
785-
workInProgress.memoizedState.transitions !== null
786-
) {
780+
if (enableTransitionTracing) {
787781
// We have now gone from hidden to visible, so any transitions should
788782
// be added to the stack to get added to any Offscreen/suspense children
789-
transitions = workInProgress.memoizedState.transitions;
783+
transitions = workInProgress.stateNode.transitions;
790784
}
791785

792786
pushTransition(workInProgress, prevCachePool, transitions);
@@ -1323,8 +1317,7 @@ function updateHostRoot(current, workInProgress, renderLanes) {
13231317
element: nextChildren,
13241318
isDehydrated: false,
13251319
cache: nextState.cache,
1326-
pendingSuspenseBoundaries: nextState.pendingSuspenseBoundaries,
1327-
transitions: nextState.transitions,
1320+
incompleteTransitions: nextState.incompleteTransitions,
13281321
};
13291322
const updateQueue: UpdateQueue<RootState> = (workInProgress.updateQueue: any);
13301323
// `baseState` can always be the last state because the root doesn't
@@ -1920,7 +1913,6 @@ function mountSuspenseOffscreenState(renderLanes: Lanes): OffscreenState {
19201913
return {
19211914
baseLanes: renderLanes,
19221915
cachePool: getSuspendedCache(),
1923-
transitions: null,
19241916
};
19251917
}
19261918

@@ -1955,7 +1947,6 @@ function updateSuspenseOffscreenState(
19551947
return {
19561948
baseLanes: mergeLanes(prevOffscreenState.baseLanes, renderLanes),
19571949
cachePool,
1958-
transitions: prevOffscreenState.transitions,
19591950
};
19601951
}
19611952

packages/react-reconciler/src/ReactFiberBeginWork.old.js

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -675,7 +675,6 @@ function updateOffscreenComponent(
675675
const nextState: OffscreenState = {
676676
baseLanes: NoLanes,
677677
cachePool: null,
678-
transitions: null,
679678
};
680679
workInProgress.memoizedState = nextState;
681680
if (enableCache) {
@@ -709,7 +708,6 @@ function updateOffscreenComponent(
709708
const nextState: OffscreenState = {
710709
baseLanes: nextBaseLanes,
711710
cachePool: spawnedCachePool,
712-
transitions: null,
713711
};
714712
workInProgress.memoizedState = nextState;
715713
workInProgress.updateQueue = null;
@@ -745,7 +743,6 @@ function updateOffscreenComponent(
745743
const nextState: OffscreenState = {
746744
baseLanes: NoLanes,
747745
cachePool: null,
748-
transitions: null,
749746
};
750747
workInProgress.memoizedState = nextState;
751748
// Push the lanes that were skipped when we bailed out.
@@ -780,13 +777,10 @@ function updateOffscreenComponent(
780777
}
781778

782779
let transitions = null;
783-
if (
784-
workInProgress.memoizedState !== null &&
785-
workInProgress.memoizedState.transitions !== null
786-
) {
780+
if (enableTransitionTracing) {
787781
// We have now gone from hidden to visible, so any transitions should
788782
// be added to the stack to get added to any Offscreen/suspense children
789-
transitions = workInProgress.memoizedState.transitions;
783+
transitions = workInProgress.stateNode.transitions;
790784
}
791785

792786
pushTransition(workInProgress, prevCachePool, transitions);
@@ -1323,8 +1317,7 @@ function updateHostRoot(current, workInProgress, renderLanes) {
13231317
element: nextChildren,
13241318
isDehydrated: false,
13251319
cache: nextState.cache,
1326-
pendingSuspenseBoundaries: nextState.pendingSuspenseBoundaries,
1327-
transitions: nextState.transitions,
1320+
incompleteTransitions: nextState.incompleteTransitions,
13281321
};
13291322
const updateQueue: UpdateQueue<RootState> = (workInProgress.updateQueue: any);
13301323
// `baseState` can always be the last state because the root doesn't
@@ -1920,7 +1913,6 @@ function mountSuspenseOffscreenState(renderLanes: Lanes): OffscreenState {
19201913
return {
19211914
baseLanes: renderLanes,
19221915
cachePool: getSuspendedCache(),
1923-
transitions: null,
19241916
};
19251917
}
19261918

@@ -1955,7 +1947,6 @@ function updateSuspenseOffscreenState(
19551947
return {
19561948
baseLanes: mergeLanes(prevOffscreenState.baseLanes, renderLanes),
19571949
cachePool,
1958-
transitions: prevOffscreenState.transitions,
19591950
};
19601951
}
19611952

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

Lines changed: 81 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1066,10 +1066,7 @@ function reappearLayoutEffectsOnFiber(node: Fiber) {
10661066
}
10671067
}
10681068

1069-
function commitTransitionProgress(
1070-
finishedRoot: FiberRoot,
1071-
offscreenFiber: Fiber,
1072-
) {
1069+
function commitTransitionProgress(offscreenFiber: Fiber) {
10731070
if (enableTransitionTracing) {
10741071
// This function adds suspense boundaries to the root
10751072
// or tracing marker's pendingSuspenseBoundaries map.
@@ -1094,12 +1091,7 @@ function commitTransitionProgress(
10941091
const wasHidden = prevState !== null;
10951092
const isHidden = nextState !== null;
10961093

1097-
const rootState: RootState = finishedRoot.current.memoizedState;
1098-
// TODO(luna) move pendingSuspenseBoundaries and transitions from
1099-
// HostRoot fiber to FiberRoot
1100-
const rootPendingBoundaries = rootState.pendingSuspenseBoundaries;
1101-
const rootTransitions = rootState.transitions;
1102-
1094+
const pendingMarkers = offscreenInstance.pendingMarkers;
11031095
// If there is a name on the suspense boundary, store that in
11041096
// the pending boundaries.
11051097
let name = null;
@@ -1112,38 +1104,26 @@ function commitTransitionProgress(
11121104
name = parent.memoizedProps.unstable_name;
11131105
}
11141106

1115-
if (rootPendingBoundaries !== null) {
1116-
if (previousFiber === null) {
1117-
// Initial mount
1118-
if (isHidden) {
1119-
rootPendingBoundaries.set(offscreenInstance, {
1107+
if (!wasHidden && isHidden) {
1108+
// The suspense boundaries was just hidden. Add the boundary
1109+
// to the pending boundary set if it's there
1110+
if (pendingMarkers !== null) {
1111+
pendingMarkers.forEach(pendingBoundaries => {
1112+
pendingBoundaries.set(offscreenInstance, {
11201113
name,
11211114
});
1122-
}
1123-
} else {
1124-
if (wasHidden && !isHidden) {
1125-
// The suspense boundary went from hidden to visible. Remove
1126-
// the boundary from the pending suspense boundaries set
1127-
// if it's there
1128-
if (rootPendingBoundaries.has(offscreenInstance)) {
1129-
rootPendingBoundaries.delete(offscreenInstance);
1130-
1131-
if (rootPendingBoundaries.size === 0 && rootTransitions !== null) {
1132-
rootTransitions.forEach(transition => {
1133-
addTransitionCompleteCallbackToPendingTransition({
1134-
transitionName: transition.name,
1135-
startTime: transition.startTime,
1136-
});
1137-
});
1138-
}
1115+
});
1116+
}
1117+
} else if (wasHidden && !isHidden) {
1118+
// The suspense boundary went from hidden to visible. Remove
1119+
// the boundary from the pending suspense boundaries set
1120+
// if it's there
1121+
if (pendingMarkers !== null) {
1122+
pendingMarkers.forEach(pendingBoundaries => {
1123+
if (pendingBoundaries.has(offscreenInstance)) {
1124+
pendingBoundaries.delete(offscreenInstance);
11391125
}
1140-
} else if (!wasHidden && isHidden) {
1141-
// The suspense boundaries was just hidden. Add the boundary
1142-
// to the pending boundary set if it's there
1143-
rootPendingBoundaries.set(offscreenInstance, {
1144-
name,
1145-
});
1146-
}
1126+
});
11471127
}
11481128
}
11491129
}
@@ -2830,45 +2810,46 @@ function commitPassiveMountOnFiber(
28302810
// Get the transitions that were initiatized during the render
28312811
// and add a start transition callback for each of them
28322812
const state = finishedWork.memoizedState;
2833-
// TODO Since it's a mutable field, this should live on the FiberRoot
2834-
if (state.transitions === null) {
2835-
state.transitions = new Set([]);
2836-
}
2837-
const pendingTransitions = state.transitions;
2838-
const pendingSuspenseBoundaries = state.pendingSuspenseBoundaries;
2839-
2813+
let incompleteTransitions = state.incompleteTransitions;
28402814
// Initial render
28412815
if (committedTransitions !== null) {
2816+
if (state.incompleteTransitions === null) {
2817+
state.incompleteTransitions = incompleteTransitions = new Map();
2818+
}
2819+
28422820
committedTransitions.forEach(transition => {
28432821
addTransitionStartCallbackToPendingTransition({
28442822
transitionName: transition.name,
28452823
startTime: transition.startTime,
28462824
});
2847-
pendingTransitions.add(transition);
2825+
2826+
if (!incompleteTransitions.has(transition)) {
2827+
incompleteTransitions.set(transition, null);
2828+
}
28482829
});
28492830

2850-
if (
2851-
pendingSuspenseBoundaries === null ||
2852-
pendingSuspenseBoundaries.size === 0
2853-
) {
2854-
pendingTransitions.forEach(transition => {
2831+
clearTransitionsForLanes(finishedRoot, committedLanes);
2832+
}
2833+
2834+
if (incompleteTransitions !== null) {
2835+
incompleteTransitions.forEach((pendingBoundaries, transition) => {
2836+
if (pendingBoundaries === null || pendingBoundaries.size === 0) {
28552837
addTransitionCompleteCallbackToPendingTransition({
28562838
transitionName: transition.name,
28572839
startTime: transition.startTime,
28582840
});
2859-
});
2860-
}
2861-
2862-
clearTransitionsForLanes(finishedRoot, committedLanes);
2841+
incompleteTransitions.delete(transition);
2842+
}
2843+
});
28632844
}
28642845

28652846
// If there are no more pending suspense boundaries we
28662847
// clear the transitions because they are all complete.
28672848
if (
2868-
pendingSuspenseBoundaries === null ||
2869-
pendingSuspenseBoundaries.size === 0
2849+
incompleteTransitions === null ||
2850+
incompleteTransitions.size === 0
28702851
) {
2871-
state.transitions = null;
2852+
state.incompleteTransitions = null;
28722853
}
28732854
}
28742855
break;
@@ -2909,39 +2890,59 @@ function commitPassiveMountOnFiber(
29092890
const isFallback = finishedWork.memoizedState;
29102891
const queue = (finishedWork.updateQueue: any);
29112892
const rootMemoizedState = finishedRoot.current.memoizedState;
2893+
const instance = finishedWork.stateNode;
29122894

29132895
if (queue !== null) {
2914-
// We have one instance of the pendingSuspenseBoundaries map.
2915-
// We only need one because we update it during the commit phase.
2916-
// We instantiate a new Map if we haven't already
2917-
if (rootMemoizedState.pendingSuspenseBoundaries === null) {
2918-
rootMemoizedState.pendingSuspenseBoundaries = new Map();
2919-
}
2920-
29212896
if (isFallback) {
29222897
const transitions = queue.transitions;
2923-
let prevTransitions = finishedWork.memoizedState.transitions;
2924-
// Add all the transitions saved in the update queue during
2925-
// the render phase (ie the transitions associated with this boundary)
2926-
// into the transitions set.
2927-
if (transitions !== null) {
2928-
if (prevTransitions === null) {
2929-
// We only have one instance of the transitions set
2930-
// because we update it only during the commit phase. We
2931-
// will create the set on a as needed basis in the commit phase
2932-
finishedWork.memoizedState.transitions = prevTransitions = new Set();
2933-
}
2898+
let prevTransitions = instance.transitions;
2899+
let rootIncompleteTransitions =
2900+
rootMemoizedState.incompleteTransitions;
2901+
2902+
// We lazily instantiate transition tracing relevant maps
2903+
// and sets in the commit phase as we need to use them. We only
2904+
// instantiate them in the fallback phase on an as needed basis
2905+
if (rootMemoizedState.incompleteTransitions === null) {
2906+
// TODO(luna): Move this to the fiber root
2907+
rootMemoizedState.incompleteTransitions = rootIncompleteTransitions = new Map();
2908+
}
2909+
if (instance.pendingMarkers === null) {
2910+
instance.pendingMarkers = new Set();
2911+
}
2912+
if (transitions !== null && prevTransitions === null) {
2913+
instance.transitions = prevTransitions = new Set();
2914+
}
29342915

2916+
if (transitions !== null) {
29352917
transitions.forEach(transition => {
2918+
// Add all the transitions saved in the update queue during
2919+
// the render phase (ie the transitions associated with this boundary)
2920+
// into the transitions set.
29362921
prevTransitions.add(transition);
2922+
2923+
// Add the root transition's pending suspense boundary set to
2924+
// the queue's marker set. We will iterate through the marker
2925+
// set when we toggle state on the suspense boundary and
2926+
// add or remove the pending suspense boundaries as needed.
2927+
if (!rootIncompleteTransitions.has(transition)) {
2928+
rootIncompleteTransitions.set(transition, new Map());
2929+
}
2930+
instance.pendingMarkers.add(
2931+
rootIncompleteTransitions.get(transition),
2932+
);
29372933
});
29382934
}
29392935
}
2940-
}
29412936

2942-
commitTransitionProgress(finishedRoot, finishedWork);
2937+
commitTransitionProgress(finishedWork);
29432938

2944-
finishedWork.updateQueue = null;
2939+
if (
2940+
instance.pendingMarkers === null ||
2941+
instance.pendingMarkers.size === 0
2942+
) {
2943+
finishedWork.updateQueue = null;
2944+
}
2945+
}
29452946
}
29462947

29472948
break;

0 commit comments

Comments
 (0)