mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[FORKED] Track nearest Suspense handler on stack
Instead of traversing the return path whenever something suspends to find the nearest Suspense boundary, we can push the Suspense boundary onto the stack before entering its subtree. This doesn't affect the overall algorithm that much, but because we already do all the same logic in the begin phase, we can save some redundant work by tracking that information on the stack instead of recomputing it every time.
This commit is contained in:
@@ -37,7 +37,6 @@ import type {UpdateQueue} from './ReactFiberClassUpdateQueue.new';
|
|||||||
import type {RootState} from './ReactFiberRoot.new';
|
import type {RootState} from './ReactFiberRoot.new';
|
||||||
import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.new';
|
import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.new';
|
||||||
import {
|
import {
|
||||||
enableSuspenseAvoidThisFallback,
|
|
||||||
enableCPUSuspense,
|
enableCPUSuspense,
|
||||||
enableUseMutableSource,
|
enableUseMutableSource,
|
||||||
} from 'shared/ReactFeatureFlags';
|
} from 'shared/ReactFeatureFlags';
|
||||||
@@ -167,13 +166,14 @@ import {shouldError, shouldSuspend} from './ReactFiberReconciler';
|
|||||||
import {pushHostContext, pushHostContainer} from './ReactFiberHostContext.new';
|
import {pushHostContext, pushHostContainer} from './ReactFiberHostContext.new';
|
||||||
import {
|
import {
|
||||||
suspenseStackCursor,
|
suspenseStackCursor,
|
||||||
pushSuspenseContext,
|
pushSuspenseListContext,
|
||||||
InvisibleParentSuspenseContext,
|
|
||||||
ForceSuspenseFallback,
|
ForceSuspenseFallback,
|
||||||
hasSuspenseContext,
|
hasSuspenseListContext,
|
||||||
setDefaultShallowSuspenseContext,
|
setDefaultShallowSuspenseListContext,
|
||||||
addSubtreeSuspenseContext,
|
setShallowSuspenseListContext,
|
||||||
setShallowSuspenseContext,
|
pushPrimaryTreeSuspenseHandler,
|
||||||
|
pushFallbackTreeSuspenseHandler,
|
||||||
|
popSuspenseHandler,
|
||||||
} from './ReactFiberSuspenseContext.new';
|
} from './ReactFiberSuspenseContext.new';
|
||||||
import {
|
import {
|
||||||
pushHiddenContext,
|
pushHiddenContext,
|
||||||
@@ -1969,7 +1969,6 @@ function updateSuspenseOffscreenState(
|
|||||||
|
|
||||||
// TODO: Probably should inline this back
|
// TODO: Probably should inline this back
|
||||||
function shouldRemainOnFallback(
|
function shouldRemainOnFallback(
|
||||||
suspenseContext: SuspenseContext,
|
|
||||||
current: null | Fiber,
|
current: null | Fiber,
|
||||||
workInProgress: Fiber,
|
workInProgress: Fiber,
|
||||||
renderLanes: Lanes,
|
renderLanes: Lanes,
|
||||||
@@ -1989,7 +1988,8 @@ function shouldRemainOnFallback(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Not currently showing content. Consult the Suspense context.
|
// Not currently showing content. Consult the Suspense context.
|
||||||
return hasSuspenseContext(
|
const suspenseContext: SuspenseContext = suspenseStackCursor.current;
|
||||||
|
return hasSuspenseListContext(
|
||||||
suspenseContext,
|
suspenseContext,
|
||||||
(ForceSuspenseFallback: SuspenseContext),
|
(ForceSuspenseFallback: SuspenseContext),
|
||||||
);
|
);
|
||||||
@@ -2010,50 +2010,18 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let suspenseContext: SuspenseContext = suspenseStackCursor.current;
|
|
||||||
|
|
||||||
let showFallback = false;
|
let showFallback = false;
|
||||||
const didSuspend = (workInProgress.flags & DidCapture) !== NoFlags;
|
const didSuspend = (workInProgress.flags & DidCapture) !== NoFlags;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
didSuspend ||
|
didSuspend ||
|
||||||
shouldRemainOnFallback(
|
shouldRemainOnFallback(current, workInProgress, renderLanes)
|
||||||
suspenseContext,
|
|
||||||
current,
|
|
||||||
workInProgress,
|
|
||||||
renderLanes,
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
// Something in this boundary's subtree already suspended. Switch to
|
// Something in this boundary's subtree already suspended. Switch to
|
||||||
// rendering the fallback children.
|
// rendering the fallback children.
|
||||||
showFallback = true;
|
showFallback = true;
|
||||||
workInProgress.flags &= ~DidCapture;
|
workInProgress.flags &= ~DidCapture;
|
||||||
} else {
|
|
||||||
// Attempting the main content
|
|
||||||
if (
|
|
||||||
current === null ||
|
|
||||||
(current.memoizedState: null | SuspenseState) !== null
|
|
||||||
) {
|
|
||||||
// This is a new mount or this boundary is already showing a fallback state.
|
|
||||||
// Mark this subtree context as having at least one invisible parent that could
|
|
||||||
// handle the fallback state.
|
|
||||||
// Avoided boundaries are not considered since they cannot handle preferred fallback states.
|
|
||||||
if (
|
|
||||||
!enableSuspenseAvoidThisFallback ||
|
|
||||||
nextProps.unstable_avoidThisFallback !== true
|
|
||||||
) {
|
|
||||||
suspenseContext = addSubtreeSuspenseContext(
|
|
||||||
suspenseContext,
|
|
||||||
InvisibleParentSuspenseContext,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspenseContext = setDefaultShallowSuspenseContext(suspenseContext);
|
|
||||||
|
|
||||||
pushSuspenseContext(workInProgress, suspenseContext);
|
|
||||||
|
|
||||||
// OK, the next part is confusing. We're about to reconcile the Suspense
|
// OK, the next part is confusing. We're about to reconcile the Suspense
|
||||||
// boundary's children. This involves some custom reconciliation logic. Two
|
// boundary's children. This involves some custom reconciliation logic. Two
|
||||||
// main reasons this is so complicated.
|
// main reasons this is so complicated.
|
||||||
@@ -2081,24 +2049,40 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
|
|||||||
|
|
||||||
// Special path for hydration
|
// Special path for hydration
|
||||||
// If we're currently hydrating, try to hydrate this boundary.
|
// If we're currently hydrating, try to hydrate this boundary.
|
||||||
tryToClaimNextHydratableInstance(workInProgress);
|
if (getIsHydrating()) {
|
||||||
// This could've been a dehydrated suspense component.
|
// We must push the suspense handler context *before* attempting to
|
||||||
const suspenseState: null | SuspenseState = workInProgress.memoizedState;
|
// hydrate, to avoid a mismatch in case it errors.
|
||||||
if (suspenseState !== null) {
|
if (showFallback) {
|
||||||
const dehydrated = suspenseState.dehydrated;
|
pushPrimaryTreeSuspenseHandler(workInProgress);
|
||||||
if (dehydrated !== null) {
|
} else {
|
||||||
return mountDehydratedSuspenseComponent(
|
pushFallbackTreeSuspenseHandler(workInProgress);
|
||||||
workInProgress,
|
|
||||||
dehydrated,
|
|
||||||
renderLanes,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
tryToClaimNextHydratableInstance(workInProgress);
|
||||||
|
// This could've been a dehydrated suspense component.
|
||||||
|
const suspenseState: null | SuspenseState = workInProgress.memoizedState;
|
||||||
|
if (suspenseState !== null) {
|
||||||
|
const dehydrated = suspenseState.dehydrated;
|
||||||
|
if (dehydrated !== null) {
|
||||||
|
return mountDehydratedSuspenseComponent(
|
||||||
|
workInProgress,
|
||||||
|
dehydrated,
|
||||||
|
renderLanes,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If hydration didn't succeed, fall through to the normal Suspense path.
|
||||||
|
// To avoid a stack mismatch we need to pop the Suspense handler that we
|
||||||
|
// pushed above. This will become less awkward when move the hydration
|
||||||
|
// logic to its own fiber.
|
||||||
|
popSuspenseHandler(workInProgress);
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextPrimaryChildren = nextProps.children;
|
const nextPrimaryChildren = nextProps.children;
|
||||||
const nextFallbackChildren = nextProps.fallback;
|
const nextFallbackChildren = nextProps.fallback;
|
||||||
|
|
||||||
if (showFallback) {
|
if (showFallback) {
|
||||||
|
pushFallbackTreeSuspenseHandler(workInProgress);
|
||||||
|
|
||||||
const fallbackFragment = mountSuspenseFallbackChildren(
|
const fallbackFragment = mountSuspenseFallbackChildren(
|
||||||
workInProgress,
|
workInProgress,
|
||||||
nextPrimaryChildren,
|
nextPrimaryChildren,
|
||||||
@@ -2131,6 +2115,7 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
|
|||||||
// This is a CPU-bound tree. Skip this tree and show a placeholder to
|
// This is a CPU-bound tree. Skip this tree and show a placeholder to
|
||||||
// unblock the surrounding content. Then immediately retry after the
|
// unblock the surrounding content. Then immediately retry after the
|
||||||
// initial commit.
|
// initial commit.
|
||||||
|
pushFallbackTreeSuspenseHandler(workInProgress);
|
||||||
const fallbackFragment = mountSuspenseFallbackChildren(
|
const fallbackFragment = mountSuspenseFallbackChildren(
|
||||||
workInProgress,
|
workInProgress,
|
||||||
nextPrimaryChildren,
|
nextPrimaryChildren,
|
||||||
@@ -2154,6 +2139,7 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
|
|||||||
workInProgress.lanes = SomeRetryLane;
|
workInProgress.lanes = SomeRetryLane;
|
||||||
return fallbackFragment;
|
return fallbackFragment;
|
||||||
} else {
|
} else {
|
||||||
|
pushPrimaryTreeSuspenseHandler(workInProgress);
|
||||||
return mountSuspensePrimaryChildren(
|
return mountSuspensePrimaryChildren(
|
||||||
workInProgress,
|
workInProgress,
|
||||||
nextPrimaryChildren,
|
nextPrimaryChildren,
|
||||||
@@ -2181,6 +2167,8 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (showFallback) {
|
if (showFallback) {
|
||||||
|
pushFallbackTreeSuspenseHandler(workInProgress);
|
||||||
|
|
||||||
const nextFallbackChildren = nextProps.fallback;
|
const nextFallbackChildren = nextProps.fallback;
|
||||||
const nextPrimaryChildren = nextProps.children;
|
const nextPrimaryChildren = nextProps.children;
|
||||||
const fallbackChildFragment = updateSuspenseFallbackChildren(
|
const fallbackChildFragment = updateSuspenseFallbackChildren(
|
||||||
@@ -2215,6 +2203,8 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
|
|||||||
workInProgress.memoizedState = SUSPENDED_MARKER;
|
workInProgress.memoizedState = SUSPENDED_MARKER;
|
||||||
return fallbackChildFragment;
|
return fallbackChildFragment;
|
||||||
} else {
|
} else {
|
||||||
|
pushPrimaryTreeSuspenseHandler(workInProgress);
|
||||||
|
|
||||||
const nextPrimaryChildren = nextProps.children;
|
const nextPrimaryChildren = nextProps.children;
|
||||||
const primaryChildFragment = updateSuspensePrimaryChildren(
|
const primaryChildFragment = updateSuspensePrimaryChildren(
|
||||||
current,
|
current,
|
||||||
@@ -2585,6 +2575,7 @@ function updateDehydratedSuspenseComponent(
|
|||||||
): null | Fiber {
|
): null | Fiber {
|
||||||
if (!didSuspend) {
|
if (!didSuspend) {
|
||||||
// This is the first render pass. Attempt to hydrate.
|
// This is the first render pass. Attempt to hydrate.
|
||||||
|
pushPrimaryTreeSuspenseHandler(workInProgress);
|
||||||
|
|
||||||
// We should never be hydrating at this point because it is the first pass,
|
// We should never be hydrating at this point because it is the first pass,
|
||||||
// but after we've already committed once.
|
// but after we've already committed once.
|
||||||
@@ -2751,6 +2742,8 @@ function updateDehydratedSuspenseComponent(
|
|||||||
|
|
||||||
if (workInProgress.flags & ForceClientRender) {
|
if (workInProgress.flags & ForceClientRender) {
|
||||||
// Something errored during hydration. Try again without hydrating.
|
// Something errored during hydration. Try again without hydrating.
|
||||||
|
pushPrimaryTreeSuspenseHandler(workInProgress);
|
||||||
|
|
||||||
workInProgress.flags &= ~ForceClientRender;
|
workInProgress.flags &= ~ForceClientRender;
|
||||||
const capturedValue = createCapturedValue(
|
const capturedValue = createCapturedValue(
|
||||||
new Error(
|
new Error(
|
||||||
@@ -2767,6 +2760,10 @@ function updateDehydratedSuspenseComponent(
|
|||||||
} else if ((workInProgress.memoizedState: null | SuspenseState) !== null) {
|
} else if ((workInProgress.memoizedState: null | SuspenseState) !== null) {
|
||||||
// Something suspended and we should still be in dehydrated mode.
|
// Something suspended and we should still be in dehydrated mode.
|
||||||
// Leave the existing child in place.
|
// Leave the existing child in place.
|
||||||
|
|
||||||
|
// Push to avoid a mismatch
|
||||||
|
pushFallbackTreeSuspenseHandler(workInProgress);
|
||||||
|
|
||||||
workInProgress.child = current.child;
|
workInProgress.child = current.child;
|
||||||
// The dehydrated completion pass expects this flag to be there
|
// The dehydrated completion pass expects this flag to be there
|
||||||
// but the normal suspense pass doesn't.
|
// but the normal suspense pass doesn't.
|
||||||
@@ -2775,6 +2772,8 @@ function updateDehydratedSuspenseComponent(
|
|||||||
} else {
|
} else {
|
||||||
// Suspended but we should no longer be in dehydrated mode.
|
// Suspended but we should no longer be in dehydrated mode.
|
||||||
// Therefore we now have to render the fallback.
|
// Therefore we now have to render the fallback.
|
||||||
|
pushFallbackTreeSuspenseHandler(workInProgress);
|
||||||
|
|
||||||
const nextPrimaryChildren = nextProps.children;
|
const nextPrimaryChildren = nextProps.children;
|
||||||
const nextFallbackChildren = nextProps.fallback;
|
const nextFallbackChildren = nextProps.fallback;
|
||||||
const fallbackChildFragment = mountSuspenseFallbackAfterRetryWithoutHydrating(
|
const fallbackChildFragment = mountSuspenseFallbackAfterRetryWithoutHydrating(
|
||||||
@@ -3070,12 +3069,12 @@ function updateSuspenseListComponent(
|
|||||||
|
|
||||||
let suspenseContext: SuspenseContext = suspenseStackCursor.current;
|
let suspenseContext: SuspenseContext = suspenseStackCursor.current;
|
||||||
|
|
||||||
const shouldForceFallback = hasSuspenseContext(
|
const shouldForceFallback = hasSuspenseListContext(
|
||||||
suspenseContext,
|
suspenseContext,
|
||||||
(ForceSuspenseFallback: SuspenseContext),
|
(ForceSuspenseFallback: SuspenseContext),
|
||||||
);
|
);
|
||||||
if (shouldForceFallback) {
|
if (shouldForceFallback) {
|
||||||
suspenseContext = setShallowSuspenseContext(
|
suspenseContext = setShallowSuspenseListContext(
|
||||||
suspenseContext,
|
suspenseContext,
|
||||||
ForceSuspenseFallback,
|
ForceSuspenseFallback,
|
||||||
);
|
);
|
||||||
@@ -3093,9 +3092,9 @@ function updateSuspenseListComponent(
|
|||||||
renderLanes,
|
renderLanes,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
suspenseContext = setDefaultShallowSuspenseContext(suspenseContext);
|
suspenseContext = setDefaultShallowSuspenseListContext(suspenseContext);
|
||||||
}
|
}
|
||||||
pushSuspenseContext(workInProgress, suspenseContext);
|
pushSuspenseListContext(workInProgress, suspenseContext);
|
||||||
|
|
||||||
if ((workInProgress.mode & ConcurrentMode) === NoMode) {
|
if ((workInProgress.mode & ConcurrentMode) === NoMode) {
|
||||||
// In legacy mode, SuspenseList doesn't work so we just
|
// In legacy mode, SuspenseList doesn't work so we just
|
||||||
@@ -3559,10 +3558,9 @@ function attemptEarlyBailoutIfNoScheduledUpdate(
|
|||||||
const state: SuspenseState | null = workInProgress.memoizedState;
|
const state: SuspenseState | null = workInProgress.memoizedState;
|
||||||
if (state !== null) {
|
if (state !== null) {
|
||||||
if (state.dehydrated !== null) {
|
if (state.dehydrated !== null) {
|
||||||
pushSuspenseContext(
|
// We're not going to render the children, so this is just to maintain
|
||||||
workInProgress,
|
// push/pop symmetry
|
||||||
setDefaultShallowSuspenseContext(suspenseStackCursor.current),
|
pushPrimaryTreeSuspenseHandler(workInProgress);
|
||||||
);
|
|
||||||
// We know that this component will suspend again because if it has
|
// We know that this component will suspend again because if it has
|
||||||
// been unsuspended it has committed as a resolved Suspense component.
|
// been unsuspended it has committed as a resolved Suspense component.
|
||||||
// If it needs to be retried, it should have work scheduled on it.
|
// If it needs to be retried, it should have work scheduled on it.
|
||||||
@@ -3585,10 +3583,7 @@ function attemptEarlyBailoutIfNoScheduledUpdate(
|
|||||||
} else {
|
} else {
|
||||||
// The primary child fragment does not have pending work marked
|
// The primary child fragment does not have pending work marked
|
||||||
// on it
|
// on it
|
||||||
pushSuspenseContext(
|
pushPrimaryTreeSuspenseHandler(workInProgress);
|
||||||
workInProgress,
|
|
||||||
setDefaultShallowSuspenseContext(suspenseStackCursor.current),
|
|
||||||
);
|
|
||||||
// The primary children do not have pending work with sufficient
|
// The primary children do not have pending work with sufficient
|
||||||
// priority. Bailout.
|
// priority. Bailout.
|
||||||
const child = bailoutOnAlreadyFinishedWork(
|
const child = bailoutOnAlreadyFinishedWork(
|
||||||
@@ -3608,10 +3603,7 @@ function attemptEarlyBailoutIfNoScheduledUpdate(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
pushSuspenseContext(
|
pushPrimaryTreeSuspenseHandler(workInProgress);
|
||||||
workInProgress,
|
|
||||||
setDefaultShallowSuspenseContext(suspenseStackCursor.current),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -3669,7 +3661,7 @@ function attemptEarlyBailoutIfNoScheduledUpdate(
|
|||||||
renderState.tail = null;
|
renderState.tail = null;
|
||||||
renderState.lastEffect = null;
|
renderState.lastEffect = null;
|
||||||
}
|
}
|
||||||
pushSuspenseContext(workInProgress, suspenseStackCursor.current);
|
pushSuspenseListContext(workInProgress, suspenseStackCursor.current);
|
||||||
|
|
||||||
if (hasChildWork) {
|
if (hasChildWork) {
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import type {
|
|||||||
SuspenseState,
|
SuspenseState,
|
||||||
SuspenseListRenderState,
|
SuspenseListRenderState,
|
||||||
} from './ReactFiberSuspenseComponent.new';
|
} from './ReactFiberSuspenseComponent.new';
|
||||||
import type {SuspenseContext} from './ReactFiberSuspenseContext.new';
|
|
||||||
import type {OffscreenState} from './ReactFiberOffscreenComponent';
|
import type {OffscreenState} from './ReactFiberOffscreenComponent';
|
||||||
import type {Cache} from './ReactFiberCacheComponent.new';
|
import type {Cache} from './ReactFiberCacheComponent.new';
|
||||||
import {
|
import {
|
||||||
@@ -109,15 +108,17 @@ import {
|
|||||||
} from './ReactFiberHostContext.new';
|
} from './ReactFiberHostContext.new';
|
||||||
import {
|
import {
|
||||||
suspenseStackCursor,
|
suspenseStackCursor,
|
||||||
InvisibleParentSuspenseContext,
|
popSuspenseListContext,
|
||||||
hasSuspenseContext,
|
popSuspenseHandler,
|
||||||
popSuspenseContext,
|
pushSuspenseListContext,
|
||||||
pushSuspenseContext,
|
setShallowSuspenseListContext,
|
||||||
setShallowSuspenseContext,
|
|
||||||
ForceSuspenseFallback,
|
ForceSuspenseFallback,
|
||||||
setDefaultShallowSuspenseContext,
|
setDefaultShallowSuspenseListContext,
|
||||||
} from './ReactFiberSuspenseContext.new';
|
} from './ReactFiberSuspenseContext.new';
|
||||||
import {popHiddenContext} from './ReactFiberHiddenContext.new';
|
import {
|
||||||
|
popHiddenContext,
|
||||||
|
isCurrentTreeHidden,
|
||||||
|
} from './ReactFiberHiddenContext.new';
|
||||||
import {findFirstSuspended} from './ReactFiberSuspenseComponent.new';
|
import {findFirstSuspended} from './ReactFiberSuspenseComponent.new';
|
||||||
import {
|
import {
|
||||||
isContextProvider as isLegacyContextProvider,
|
isContextProvider as isLegacyContextProvider,
|
||||||
@@ -1076,7 +1077,7 @@ function completeWork(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
case SuspenseComponent: {
|
case SuspenseComponent: {
|
||||||
popSuspenseContext(workInProgress);
|
popSuspenseHandler(workInProgress);
|
||||||
const nextState: null | SuspenseState = workInProgress.memoizedState;
|
const nextState: null | SuspenseState = workInProgress.memoizedState;
|
||||||
|
|
||||||
// Special path for dehydrated boundaries. We may eventually move this
|
// Special path for dehydrated boundaries. We may eventually move this
|
||||||
@@ -1185,25 +1186,23 @@ function completeWork(
|
|||||||
// If this render already had a ping or lower pri updates,
|
// If this render already had a ping or lower pri updates,
|
||||||
// and this is the first time we know we're going to suspend we
|
// and this is the first time we know we're going to suspend we
|
||||||
// should be able to immediately restart from within throwException.
|
// should be able to immediately restart from within throwException.
|
||||||
const hasInvisibleChildContext =
|
|
||||||
current === null &&
|
// Check if this is a "bad" fallback state or a good one. A bad
|
||||||
(workInProgress.memoizedProps.unstable_avoidThisFallback !==
|
// fallback state is one that we only show as a last resort; if this
|
||||||
true ||
|
// is a transition, we'll block it from displaying, and wait for
|
||||||
!enableSuspenseAvoidThisFallback);
|
// more data to arrive.
|
||||||
if (
|
const isBadFallback =
|
||||||
hasInvisibleChildContext ||
|
// It's bad to switch to a fallback if content is already visible
|
||||||
hasSuspenseContext(
|
(current !== null && !prevDidTimeout && !isCurrentTreeHidden()) ||
|
||||||
suspenseStackCursor.current,
|
// Experimental: Some fallbacks are always bad
|
||||||
(InvisibleParentSuspenseContext: SuspenseContext),
|
(enableSuspenseAvoidThisFallback &&
|
||||||
)
|
workInProgress.memoizedProps.unstable_avoidThisFallback ===
|
||||||
) {
|
true);
|
||||||
// If this was in an invisible tree or a new render, then showing
|
|
||||||
// this boundary is ok.
|
if (isBadFallback) {
|
||||||
renderDidSuspend();
|
|
||||||
} else {
|
|
||||||
// Otherwise, we're going to have to hide content so we should
|
|
||||||
// suspend for longer if possible.
|
|
||||||
renderDidSuspendDelayIfPossible();
|
renderDidSuspendDelayIfPossible();
|
||||||
|
} else {
|
||||||
|
renderDidSuspend();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1265,7 +1264,7 @@ function completeWork(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
case SuspenseListComponent: {
|
case SuspenseListComponent: {
|
||||||
popSuspenseContext(workInProgress);
|
popSuspenseListContext(workInProgress);
|
||||||
|
|
||||||
const renderState: null | SuspenseListRenderState =
|
const renderState: null | SuspenseListRenderState =
|
||||||
workInProgress.memoizedState;
|
workInProgress.memoizedState;
|
||||||
@@ -1331,11 +1330,11 @@ function completeWork(
|
|||||||
workInProgress.subtreeFlags = NoFlags;
|
workInProgress.subtreeFlags = NoFlags;
|
||||||
resetChildFibers(workInProgress, renderLanes);
|
resetChildFibers(workInProgress, renderLanes);
|
||||||
|
|
||||||
// Set up the Suspense Context to force suspense and immediately
|
// Set up the Suspense List Context to force suspense and
|
||||||
// rerender the children.
|
// immediately rerender the children.
|
||||||
pushSuspenseContext(
|
pushSuspenseListContext(
|
||||||
workInProgress,
|
workInProgress,
|
||||||
setShallowSuspenseContext(
|
setShallowSuspenseListContext(
|
||||||
suspenseStackCursor.current,
|
suspenseStackCursor.current,
|
||||||
ForceSuspenseFallback,
|
ForceSuspenseFallback,
|
||||||
),
|
),
|
||||||
@@ -1458,14 +1457,16 @@ function completeWork(
|
|||||||
// setting it the first time we go from not suspended to suspended.
|
// setting it the first time we go from not suspended to suspended.
|
||||||
let suspenseContext = suspenseStackCursor.current;
|
let suspenseContext = suspenseStackCursor.current;
|
||||||
if (didSuspendAlready) {
|
if (didSuspendAlready) {
|
||||||
suspenseContext = setShallowSuspenseContext(
|
suspenseContext = setShallowSuspenseListContext(
|
||||||
suspenseContext,
|
suspenseContext,
|
||||||
ForceSuspenseFallback,
|
ForceSuspenseFallback,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
suspenseContext = setDefaultShallowSuspenseContext(suspenseContext);
|
suspenseContext = setDefaultShallowSuspenseListContext(
|
||||||
|
suspenseContext,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
pushSuspenseContext(workInProgress, suspenseContext);
|
pushSuspenseListContext(workInProgress, suspenseContext);
|
||||||
// Do a pass over the next row.
|
// Do a pass over the next row.
|
||||||
// Don't bubble properties in this case.
|
// Don't bubble properties in this case.
|
||||||
return next;
|
return next;
|
||||||
|
|||||||
@@ -64,3 +64,7 @@ export function popHiddenContext(fiber: Fiber): void {
|
|||||||
pop(currentTreeHiddenStackCursor, fiber);
|
pop(currentTreeHiddenStackCursor, fiber);
|
||||||
pop(prevRenderLanesStackCursor, fiber);
|
pop(prevRenderLanesStackCursor, fiber);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isCurrentTreeHidden() {
|
||||||
|
return currentTreeHiddenStackCursor.current !== null;
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import type {SuspenseInstance} from './ReactFiberHostConfig';
|
|||||||
import type {Lane} from './ReactFiberLane.new';
|
import type {Lane} from './ReactFiberLane.new';
|
||||||
import type {TreeContext} from './ReactFiberTreeContext.new';
|
import type {TreeContext} from './ReactFiberTreeContext.new';
|
||||||
|
|
||||||
import {enableSuspenseAvoidThisFallback} from 'shared/ReactFeatureFlags';
|
|
||||||
import {SuspenseComponent, SuspenseListComponent} from './ReactWorkTags';
|
import {SuspenseComponent, SuspenseListComponent} from './ReactWorkTags';
|
||||||
import {NoFlags, DidCapture} from './ReactFiberFlags';
|
import {NoFlags, DidCapture} from './ReactFiberFlags';
|
||||||
import {
|
import {
|
||||||
@@ -67,37 +66,6 @@ export type SuspenseListRenderState = {|
|
|||||||
tailMode: SuspenseListTailMode,
|
tailMode: SuspenseListTailMode,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
export function shouldCaptureSuspense(
|
|
||||||
workInProgress: Fiber,
|
|
||||||
hasInvisibleParent: boolean,
|
|
||||||
): boolean {
|
|
||||||
// If it was the primary children that just suspended, capture and render the
|
|
||||||
// fallback. Otherwise, don't capture and bubble to the next boundary.
|
|
||||||
const nextState: SuspenseState | null = workInProgress.memoizedState;
|
|
||||||
if (nextState !== null) {
|
|
||||||
if (nextState.dehydrated !== null) {
|
|
||||||
// A dehydrated boundary always captures.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const props = workInProgress.memoizedProps;
|
|
||||||
// Regular boundaries always capture.
|
|
||||||
if (
|
|
||||||
!enableSuspenseAvoidThisFallback ||
|
|
||||||
props.unstable_avoidThisFallback !== true
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// If it's a boundary we should avoid, then we prefer to bubble up to the
|
|
||||||
// parent boundary if it is currently invisible.
|
|
||||||
if (hasInvisibleParent) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// If the parent is not able to handle it, we must handle it.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function findFirstSuspended(row: Fiber): null | Fiber {
|
export function findFirstSuspended(row: Fiber): null | Fiber {
|
||||||
let node = row;
|
let node = row;
|
||||||
while (node !== null) {
|
while (node !== null) {
|
||||||
|
|||||||
@@ -9,33 +9,96 @@
|
|||||||
|
|
||||||
import type {Fiber} from './ReactInternalTypes';
|
import type {Fiber} from './ReactInternalTypes';
|
||||||
import type {StackCursor} from './ReactFiberStack.new';
|
import type {StackCursor} from './ReactFiberStack.new';
|
||||||
|
import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
|
||||||
|
|
||||||
|
import {enableSuspenseAvoidThisFallback} from 'shared/ReactFeatureFlags';
|
||||||
import {createCursor, push, pop} from './ReactFiberStack.new';
|
import {createCursor, push, pop} from './ReactFiberStack.new';
|
||||||
|
import {isCurrentTreeHidden} from './ReactFiberHiddenContext.new';
|
||||||
|
import {SuspenseComponent} from './ReactWorkTags';
|
||||||
|
|
||||||
|
// The Suspense handler is the boundary that should capture if something
|
||||||
|
// suspends, i.e. it's the nearest `catch` block on the stack.
|
||||||
|
const suspenseHandlerStackCursor: StackCursor<Fiber | null> = createCursor(
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
|
function shouldAvoidedBoundaryCapture(
|
||||||
|
workInProgress: Fiber,
|
||||||
|
handlerOnStack: Fiber,
|
||||||
|
props: any,
|
||||||
|
): boolean {
|
||||||
|
if (enableSuspenseAvoidThisFallback) {
|
||||||
|
// If the parent is already showing content, and we're not inside a hidden
|
||||||
|
// tree, then we should show the avoided fallback.
|
||||||
|
if (handlerOnStack.alternate !== null && !isCurrentTreeHidden()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the handler on the stack is also an avoided boundary, then we should
|
||||||
|
// favor this inner one.
|
||||||
|
if (
|
||||||
|
handlerOnStack.tag === SuspenseComponent &&
|
||||||
|
handlerOnStack.memoizedProps.unstable_avoidThisFallback === true
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this avoided boundary is dehydrated, then it should capture.
|
||||||
|
const suspenseState: SuspenseState | null = workInProgress.memoizedState;
|
||||||
|
if (suspenseState !== null && suspenseState.dehydrated !== null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If none of those cases apply, then we should avoid this fallback and show
|
||||||
|
// the outer one instead.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pushPrimaryTreeSuspenseHandler(handler: Fiber): void {
|
||||||
|
const props = handler.pendingProps;
|
||||||
|
const handlerOnStack = suspenseHandlerStackCursor.current;
|
||||||
|
if (
|
||||||
|
enableSuspenseAvoidThisFallback &&
|
||||||
|
props.unstable_avoidThisFallback === true &&
|
||||||
|
handlerOnStack !== null &&
|
||||||
|
!shouldAvoidedBoundaryCapture(handler, handlerOnStack, props)
|
||||||
|
) {
|
||||||
|
// This boundary should not capture if something suspends. Reuse the
|
||||||
|
// existing handler on the stack.
|
||||||
|
push(suspenseHandlerStackCursor, handlerOnStack, handler);
|
||||||
|
} else {
|
||||||
|
// Push this handler onto the stack.
|
||||||
|
push(suspenseHandlerStackCursor, handler, handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pushFallbackTreeSuspenseHandler(fiber: Fiber): void {
|
||||||
|
// We're about to render the fallback. If something in the fallback suspends,
|
||||||
|
// it's akin to throwing inside of a `catch` block. This boundary should not
|
||||||
|
// capture. Reuse the existing handler on the stack.
|
||||||
|
push(suspenseHandlerStackCursor, getSuspenseHandler(), fiber);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSuspenseHandler(): Fiber | null {
|
||||||
|
return suspenseHandlerStackCursor.current;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function popSuspenseHandler(fiber: Fiber): void {
|
||||||
|
pop(suspenseHandlerStackCursor, fiber);
|
||||||
|
}
|
||||||
|
|
||||||
|
// SuspenseList context
|
||||||
|
// TODO: Move to a separate module? We may change the SuspenseList
|
||||||
|
// implementation to hide/show in the commit phase, anyway.
|
||||||
export opaque type SuspenseContext = number;
|
export opaque type SuspenseContext = number;
|
||||||
export opaque type SubtreeSuspenseContext: SuspenseContext = number;
|
export opaque type SubtreeSuspenseContext: SuspenseContext = number;
|
||||||
export opaque type ShallowSuspenseContext: SuspenseContext = number;
|
export opaque type ShallowSuspenseContext: SuspenseContext = number;
|
||||||
|
|
||||||
const DefaultSuspenseContext: SuspenseContext = 0b00;
|
const DefaultSuspenseContext: SuspenseContext = 0b00;
|
||||||
|
|
||||||
// The Suspense Context is split into two parts. The lower bits is
|
|
||||||
// inherited deeply down the subtree. The upper bits only affect
|
|
||||||
// this immediate suspense boundary and gets reset each new
|
|
||||||
// boundary or suspense list.
|
|
||||||
const SubtreeSuspenseContextMask: SuspenseContext = 0b01;
|
const SubtreeSuspenseContextMask: SuspenseContext = 0b01;
|
||||||
|
|
||||||
// Subtree Flags:
|
|
||||||
|
|
||||||
// InvisibleParentSuspenseContext indicates that one of our parent Suspense
|
|
||||||
// boundaries is not currently showing visible main content.
|
|
||||||
// Either because it is already showing a fallback or is not mounted at all.
|
|
||||||
// We can use this to determine if it is desirable to trigger a fallback at
|
|
||||||
// the parent. If not, then we might need to trigger undesirable boundaries
|
|
||||||
// and/or suspend the commit to avoid hiding the parent content.
|
|
||||||
export const InvisibleParentSuspenseContext: SubtreeSuspenseContext = 0b01;
|
|
||||||
|
|
||||||
// Shallow Flags:
|
|
||||||
|
|
||||||
// ForceSuspenseFallback can be used by SuspenseList to force newly added
|
// ForceSuspenseFallback can be used by SuspenseList to force newly added
|
||||||
// items into their fallback state during one of the render passes.
|
// items into their fallback state during one of the render passes.
|
||||||
export const ForceSuspenseFallback: ShallowSuspenseContext = 0b10;
|
export const ForceSuspenseFallback: ShallowSuspenseContext = 0b10;
|
||||||
@@ -44,40 +107,33 @@ export const suspenseStackCursor: StackCursor<SuspenseContext> = createCursor(
|
|||||||
DefaultSuspenseContext,
|
DefaultSuspenseContext,
|
||||||
);
|
);
|
||||||
|
|
||||||
export function hasSuspenseContext(
|
export function hasSuspenseListContext(
|
||||||
parentContext: SuspenseContext,
|
parentContext: SuspenseContext,
|
||||||
flag: SuspenseContext,
|
flag: SuspenseContext,
|
||||||
): boolean {
|
): boolean {
|
||||||
return (parentContext & flag) !== 0;
|
return (parentContext & flag) !== 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setDefaultShallowSuspenseContext(
|
export function setDefaultShallowSuspenseListContext(
|
||||||
parentContext: SuspenseContext,
|
parentContext: SuspenseContext,
|
||||||
): SuspenseContext {
|
): SuspenseContext {
|
||||||
return parentContext & SubtreeSuspenseContextMask;
|
return parentContext & SubtreeSuspenseContextMask;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setShallowSuspenseContext(
|
export function setShallowSuspenseListContext(
|
||||||
parentContext: SuspenseContext,
|
parentContext: SuspenseContext,
|
||||||
shallowContext: ShallowSuspenseContext,
|
shallowContext: ShallowSuspenseContext,
|
||||||
): SuspenseContext {
|
): SuspenseContext {
|
||||||
return (parentContext & SubtreeSuspenseContextMask) | shallowContext;
|
return (parentContext & SubtreeSuspenseContextMask) | shallowContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addSubtreeSuspenseContext(
|
export function pushSuspenseListContext(
|
||||||
parentContext: SuspenseContext,
|
|
||||||
subtreeContext: SubtreeSuspenseContext,
|
|
||||||
): SuspenseContext {
|
|
||||||
return parentContext | subtreeContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function pushSuspenseContext(
|
|
||||||
fiber: Fiber,
|
fiber: Fiber,
|
||||||
newContext: SuspenseContext,
|
newContext: SuspenseContext,
|
||||||
): void {
|
): void {
|
||||||
push(suspenseStackCursor, newContext, fiber);
|
push(suspenseStackCursor, newContext, fiber);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function popSuspenseContext(fiber: Fiber): void {
|
export function popSuspenseListContext(fiber: Fiber): void {
|
||||||
pop(suspenseStackCursor, fiber);
|
pop(suspenseStackCursor, fiber);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,13 +13,11 @@ import type {Lane, Lanes} from './ReactFiberLane.new';
|
|||||||
import type {CapturedValue} from './ReactCapturedValue';
|
import type {CapturedValue} from './ReactCapturedValue';
|
||||||
import type {Update} from './ReactFiberClassUpdateQueue.new';
|
import type {Update} from './ReactFiberClassUpdateQueue.new';
|
||||||
import type {Wakeable} from 'shared/ReactTypes';
|
import type {Wakeable} from 'shared/ReactTypes';
|
||||||
import type {SuspenseContext} from './ReactFiberSuspenseContext.new';
|
|
||||||
|
|
||||||
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
|
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
|
||||||
import {
|
import {
|
||||||
ClassComponent,
|
ClassComponent,
|
||||||
HostRoot,
|
HostRoot,
|
||||||
SuspenseComponent,
|
|
||||||
IncompleteClassComponent,
|
IncompleteClassComponent,
|
||||||
FunctionComponent,
|
FunctionComponent,
|
||||||
ForwardRef,
|
ForwardRef,
|
||||||
@@ -34,7 +32,6 @@ import {
|
|||||||
ForceUpdateForLegacySuspense,
|
ForceUpdateForLegacySuspense,
|
||||||
ForceClientRender,
|
ForceClientRender,
|
||||||
} from './ReactFiberFlags';
|
} from './ReactFiberFlags';
|
||||||
import {shouldCaptureSuspense} from './ReactFiberSuspenseComponent.new';
|
|
||||||
import {NoMode, ConcurrentMode, DebugTracingMode} from './ReactTypeOfMode';
|
import {NoMode, ConcurrentMode, DebugTracingMode} from './ReactTypeOfMode';
|
||||||
import {
|
import {
|
||||||
enableDebugTracing,
|
enableDebugTracing,
|
||||||
@@ -50,11 +47,7 @@ import {
|
|||||||
enqueueUpdate,
|
enqueueUpdate,
|
||||||
} from './ReactFiberClassUpdateQueue.new';
|
} from './ReactFiberClassUpdateQueue.new';
|
||||||
import {markFailedErrorBoundaryForHotReloading} from './ReactFiberHotReloading.new';
|
import {markFailedErrorBoundaryForHotReloading} from './ReactFiberHotReloading.new';
|
||||||
import {
|
import {getSuspenseHandler} from './ReactFiberSuspenseContext.new';
|
||||||
suspenseStackCursor,
|
|
||||||
InvisibleParentSuspenseContext,
|
|
||||||
hasSuspenseContext,
|
|
||||||
} from './ReactFiberSuspenseContext.new';
|
|
||||||
import {
|
import {
|
||||||
renderDidError,
|
renderDidError,
|
||||||
renderDidSuspendDelayIfPossible,
|
renderDidSuspendDelayIfPossible,
|
||||||
@@ -269,26 +262,6 @@ function resetSuspendedComponent(sourceFiber: Fiber, rootRenderLanes: Lanes) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNearestSuspenseBoundaryToCapture(returnFiber: Fiber) {
|
|
||||||
let node = returnFiber;
|
|
||||||
const hasInvisibleParentBoundary = hasSuspenseContext(
|
|
||||||
suspenseStackCursor.current,
|
|
||||||
(InvisibleParentSuspenseContext: SuspenseContext),
|
|
||||||
);
|
|
||||||
do {
|
|
||||||
if (
|
|
||||||
node.tag === SuspenseComponent &&
|
|
||||||
shouldCaptureSuspense(node, hasInvisibleParentBoundary)
|
|
||||||
) {
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
// This boundary already captured during this render. Continue to the next
|
|
||||||
// boundary.
|
|
||||||
node = node.return;
|
|
||||||
} while (node !== null);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function markSuspenseBoundaryShouldCapture(
|
function markSuspenseBoundaryShouldCapture(
|
||||||
suspenseBoundary: Fiber,
|
suspenseBoundary: Fiber,
|
||||||
returnFiber: Fiber,
|
returnFiber: Fiber,
|
||||||
@@ -444,7 +417,7 @@ function throwException(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Schedule the nearest Suspense to re-render the timed out view.
|
// Schedule the nearest Suspense to re-render the timed out view.
|
||||||
const suspenseBoundary = getNearestSuspenseBoundaryToCapture(returnFiber);
|
const suspenseBoundary = getSuspenseHandler();
|
||||||
if (suspenseBoundary !== null) {
|
if (suspenseBoundary !== null) {
|
||||||
suspenseBoundary.flags &= ~ForceClientRender;
|
suspenseBoundary.flags &= ~ForceClientRender;
|
||||||
markSuspenseBoundaryShouldCapture(
|
markSuspenseBoundaryShouldCapture(
|
||||||
@@ -496,7 +469,7 @@ function throwException(
|
|||||||
// This is a regular error, not a Suspense wakeable.
|
// This is a regular error, not a Suspense wakeable.
|
||||||
if (getIsHydrating() && sourceFiber.mode & ConcurrentMode) {
|
if (getIsHydrating() && sourceFiber.mode & ConcurrentMode) {
|
||||||
markDidThrowWhileHydratingDEV();
|
markDidThrowWhileHydratingDEV();
|
||||||
const suspenseBoundary = getNearestSuspenseBoundaryToCapture(returnFiber);
|
const suspenseBoundary = getSuspenseHandler();
|
||||||
// If the error was thrown during hydration, we may be able to recover by
|
// If the error was thrown during hydration, we may be able to recover by
|
||||||
// discarding the dehydrated content and switching to a client render.
|
// discarding the dehydrated content and switching to a client render.
|
||||||
// Instead of surfacing the error, find the nearest Suspense boundary
|
// Instead of surfacing the error, find the nearest Suspense boundary
|
||||||
|
|||||||
@@ -36,7 +36,10 @@ import {
|
|||||||
} from 'shared/ReactFeatureFlags';
|
} from 'shared/ReactFeatureFlags';
|
||||||
|
|
||||||
import {popHostContainer, popHostContext} from './ReactFiberHostContext.new';
|
import {popHostContainer, popHostContext} from './ReactFiberHostContext.new';
|
||||||
import {popSuspenseContext} from './ReactFiberSuspenseContext.new';
|
import {
|
||||||
|
popSuspenseListContext,
|
||||||
|
popSuspenseHandler,
|
||||||
|
} from './ReactFiberSuspenseContext.new';
|
||||||
import {popHiddenContext} from './ReactFiberHiddenContext.new';
|
import {popHiddenContext} from './ReactFiberHiddenContext.new';
|
||||||
import {resetHydrationState} from './ReactFiberHydrationContext.new';
|
import {resetHydrationState} from './ReactFiberHydrationContext.new';
|
||||||
import {
|
import {
|
||||||
@@ -109,7 +112,7 @@ function unwindWork(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
case SuspenseComponent: {
|
case SuspenseComponent: {
|
||||||
popSuspenseContext(workInProgress);
|
popSuspenseHandler(workInProgress);
|
||||||
const suspenseState: null | SuspenseState = workInProgress.memoizedState;
|
const suspenseState: null | SuspenseState = workInProgress.memoizedState;
|
||||||
if (suspenseState !== null && suspenseState.dehydrated !== null) {
|
if (suspenseState !== null && suspenseState.dehydrated !== null) {
|
||||||
if (workInProgress.alternate === null) {
|
if (workInProgress.alternate === null) {
|
||||||
@@ -137,7 +140,7 @@ function unwindWork(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
case SuspenseListComponent: {
|
case SuspenseListComponent: {
|
||||||
popSuspenseContext(workInProgress);
|
popSuspenseListContext(workInProgress);
|
||||||
// SuspenseList doesn't actually catch anything. It should've been
|
// SuspenseList doesn't actually catch anything. It should've been
|
||||||
// caught by a nested boundary. If not, it should bubble through.
|
// caught by a nested boundary. If not, it should bubble through.
|
||||||
return null;
|
return null;
|
||||||
@@ -208,10 +211,10 @@ function unwindInterruptedWork(
|
|||||||
popHostContainer(interruptedWork);
|
popHostContainer(interruptedWork);
|
||||||
break;
|
break;
|
||||||
case SuspenseComponent:
|
case SuspenseComponent:
|
||||||
popSuspenseContext(interruptedWork);
|
popSuspenseHandler(interruptedWork);
|
||||||
break;
|
break;
|
||||||
case SuspenseListComponent:
|
case SuspenseListComponent:
|
||||||
popSuspenseContext(interruptedWork);
|
popSuspenseListContext(interruptedWork);
|
||||||
break;
|
break;
|
||||||
case ContextProvider:
|
case ContextProvider:
|
||||||
const context: ReactContext<any> = interruptedWork.type._context;
|
const context: ReactContext<any> = interruptedWork.type._context;
|
||||||
|
|||||||
Reference in New Issue
Block a user