mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
e06c72fcf4
This is landed everywhere
285 lines
8.9 KiB
JavaScript
285 lines
8.9 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow
|
|
*/
|
|
|
|
import type {ReactContext} from 'shared/ReactTypes';
|
|
import type {Fiber, FiberRoot} from './ReactInternalTypes';
|
|
import type {Lanes} from './ReactFiberLane';
|
|
import type {SuspenseState} from './ReactFiberSuspenseComponent';
|
|
import type {Cache} from './ReactFiberCacheComponent';
|
|
import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent';
|
|
|
|
import {
|
|
ClassComponent,
|
|
HostRoot,
|
|
HostComponent,
|
|
HostHoistable,
|
|
HostSingleton,
|
|
HostPortal,
|
|
ContextProvider,
|
|
SuspenseComponent,
|
|
SuspenseListComponent,
|
|
OffscreenComponent,
|
|
LegacyHiddenComponent,
|
|
CacheComponent,
|
|
TracingMarkerComponent,
|
|
} from './ReactWorkTags';
|
|
import {DidCapture, NoFlags, ShouldCapture} from './ReactFiberFlags';
|
|
import {NoMode, ProfileMode} from './ReactTypeOfMode';
|
|
import {
|
|
enableProfilerTimer,
|
|
enableTransitionTracing,
|
|
enableRenderableContext,
|
|
} from 'shared/ReactFeatureFlags';
|
|
|
|
import {popHostContainer, popHostContext} from './ReactFiberHostContext';
|
|
import {
|
|
popSuspenseListContext,
|
|
popSuspenseHandler,
|
|
} from './ReactFiberSuspenseContext';
|
|
import {popHiddenContext} from './ReactFiberHiddenContext';
|
|
import {resetHydrationState} from './ReactFiberHydrationContext';
|
|
import {
|
|
isContextProvider as isLegacyContextProvider,
|
|
popContext as popLegacyContext,
|
|
popTopLevelContextObject as popTopLevelLegacyContextObject,
|
|
} from './ReactFiberContext';
|
|
import {popProvider} from './ReactFiberNewContext';
|
|
import {popCacheProvider} from './ReactFiberCacheComponent';
|
|
import {transferActualDuration} from './ReactProfilerTimer';
|
|
import {popTreeContext} from './ReactFiberTreeContext';
|
|
import {popRootTransition, popTransition} from './ReactFiberTransition';
|
|
import {
|
|
popMarkerInstance,
|
|
popRootMarkerInstance,
|
|
} from './ReactFiberTracingMarkerComponent';
|
|
|
|
function unwindWork(
|
|
current: Fiber | null,
|
|
workInProgress: Fiber,
|
|
renderLanes: Lanes,
|
|
): Fiber | null {
|
|
// Note: This intentionally doesn't check if we're hydrating because comparing
|
|
// to the current tree provider fiber is just as fast and less error-prone.
|
|
// Ideally we would have a special version of the work loop only
|
|
// for hydration.
|
|
popTreeContext(workInProgress);
|
|
switch (workInProgress.tag) {
|
|
case ClassComponent: {
|
|
const Component = workInProgress.type;
|
|
if (isLegacyContextProvider(Component)) {
|
|
popLegacyContext(workInProgress);
|
|
}
|
|
const flags = workInProgress.flags;
|
|
if (flags & ShouldCapture) {
|
|
workInProgress.flags = (flags & ~ShouldCapture) | DidCapture;
|
|
if (
|
|
enableProfilerTimer &&
|
|
(workInProgress.mode & ProfileMode) !== NoMode
|
|
) {
|
|
transferActualDuration(workInProgress);
|
|
}
|
|
return workInProgress;
|
|
}
|
|
return null;
|
|
}
|
|
case HostRoot: {
|
|
const root: FiberRoot = workInProgress.stateNode;
|
|
const cache: Cache = workInProgress.memoizedState.cache;
|
|
popCacheProvider(workInProgress, cache);
|
|
|
|
if (enableTransitionTracing) {
|
|
popRootMarkerInstance(workInProgress);
|
|
}
|
|
|
|
popRootTransition(workInProgress, root, renderLanes);
|
|
popHostContainer(workInProgress);
|
|
popTopLevelLegacyContextObject(workInProgress);
|
|
const flags = workInProgress.flags;
|
|
if (
|
|
(flags & ShouldCapture) !== NoFlags &&
|
|
(flags & DidCapture) === NoFlags
|
|
) {
|
|
// There was an error during render that wasn't captured by a suspense
|
|
// boundary. Do a second pass on the root to unmount the children.
|
|
workInProgress.flags = (flags & ~ShouldCapture) | DidCapture;
|
|
return workInProgress;
|
|
}
|
|
// We unwound to the root without completing it. Exit.
|
|
return null;
|
|
}
|
|
case HostHoistable:
|
|
case HostSingleton:
|
|
case HostComponent: {
|
|
// TODO: popHydrationState
|
|
popHostContext(workInProgress);
|
|
return null;
|
|
}
|
|
case SuspenseComponent: {
|
|
popSuspenseHandler(workInProgress);
|
|
const suspenseState: null | SuspenseState = workInProgress.memoizedState;
|
|
if (suspenseState !== null && suspenseState.dehydrated !== null) {
|
|
if (workInProgress.alternate === null) {
|
|
throw new Error(
|
|
'Threw in newly mounted dehydrated component. This is likely a bug in ' +
|
|
'React. Please file an issue.',
|
|
);
|
|
}
|
|
|
|
resetHydrationState();
|
|
}
|
|
|
|
const flags = workInProgress.flags;
|
|
if (flags & ShouldCapture) {
|
|
workInProgress.flags = (flags & ~ShouldCapture) | DidCapture;
|
|
// Captured a suspense effect. Re-render the boundary.
|
|
if (
|
|
enableProfilerTimer &&
|
|
(workInProgress.mode & ProfileMode) !== NoMode
|
|
) {
|
|
transferActualDuration(workInProgress);
|
|
}
|
|
return workInProgress;
|
|
}
|
|
return null;
|
|
}
|
|
case SuspenseListComponent: {
|
|
popSuspenseListContext(workInProgress);
|
|
// SuspenseList doesn't actually catch anything. It should've been
|
|
// caught by a nested boundary. If not, it should bubble through.
|
|
return null;
|
|
}
|
|
case HostPortal:
|
|
popHostContainer(workInProgress);
|
|
return null;
|
|
case ContextProvider:
|
|
let context: ReactContext<any>;
|
|
if (enableRenderableContext) {
|
|
context = workInProgress.type;
|
|
} else {
|
|
context = workInProgress.type._context;
|
|
}
|
|
popProvider(context, workInProgress);
|
|
return null;
|
|
case OffscreenComponent:
|
|
case LegacyHiddenComponent: {
|
|
popSuspenseHandler(workInProgress);
|
|
popHiddenContext(workInProgress);
|
|
popTransition(workInProgress, current);
|
|
const flags = workInProgress.flags;
|
|
if (flags & ShouldCapture) {
|
|
workInProgress.flags = (flags & ~ShouldCapture) | DidCapture;
|
|
// Captured a suspense effect. Re-render the boundary.
|
|
if (
|
|
enableProfilerTimer &&
|
|
(workInProgress.mode & ProfileMode) !== NoMode
|
|
) {
|
|
transferActualDuration(workInProgress);
|
|
}
|
|
return workInProgress;
|
|
}
|
|
return null;
|
|
}
|
|
case CacheComponent:
|
|
const cache: Cache = workInProgress.memoizedState.cache;
|
|
popCacheProvider(workInProgress, cache);
|
|
return null;
|
|
case TracingMarkerComponent:
|
|
if (enableTransitionTracing) {
|
|
if (workInProgress.stateNode !== null) {
|
|
popMarkerInstance(workInProgress);
|
|
}
|
|
}
|
|
return null;
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function unwindInterruptedWork(
|
|
current: Fiber | null,
|
|
interruptedWork: Fiber,
|
|
renderLanes: Lanes,
|
|
) {
|
|
// Note: This intentionally doesn't check if we're hydrating because comparing
|
|
// to the current tree provider fiber is just as fast and less error-prone.
|
|
// Ideally we would have a special version of the work loop only
|
|
// for hydration.
|
|
popTreeContext(interruptedWork);
|
|
switch (interruptedWork.tag) {
|
|
case ClassComponent: {
|
|
const childContextTypes = interruptedWork.type.childContextTypes;
|
|
if (childContextTypes !== null && childContextTypes !== undefined) {
|
|
popLegacyContext(interruptedWork);
|
|
}
|
|
break;
|
|
}
|
|
case HostRoot: {
|
|
const root: FiberRoot = interruptedWork.stateNode;
|
|
const cache: Cache = interruptedWork.memoizedState.cache;
|
|
popCacheProvider(interruptedWork, cache);
|
|
|
|
if (enableTransitionTracing) {
|
|
popRootMarkerInstance(interruptedWork);
|
|
}
|
|
|
|
popRootTransition(interruptedWork, root, renderLanes);
|
|
popHostContainer(interruptedWork);
|
|
popTopLevelLegacyContextObject(interruptedWork);
|
|
break;
|
|
}
|
|
case HostHoistable:
|
|
case HostSingleton:
|
|
case HostComponent: {
|
|
popHostContext(interruptedWork);
|
|
break;
|
|
}
|
|
case HostPortal:
|
|
popHostContainer(interruptedWork);
|
|
break;
|
|
case SuspenseComponent:
|
|
popSuspenseHandler(interruptedWork);
|
|
break;
|
|
case SuspenseListComponent:
|
|
popSuspenseListContext(interruptedWork);
|
|
break;
|
|
case ContextProvider:
|
|
let context: ReactContext<any>;
|
|
if (enableRenderableContext) {
|
|
context = interruptedWork.type;
|
|
} else {
|
|
context = interruptedWork.type._context;
|
|
}
|
|
popProvider(context, interruptedWork);
|
|
break;
|
|
case OffscreenComponent:
|
|
case LegacyHiddenComponent:
|
|
popSuspenseHandler(interruptedWork);
|
|
popHiddenContext(interruptedWork);
|
|
popTransition(interruptedWork, current);
|
|
break;
|
|
case CacheComponent:
|
|
const cache: Cache = interruptedWork.memoizedState.cache;
|
|
popCacheProvider(interruptedWork, cache);
|
|
break;
|
|
case TracingMarkerComponent:
|
|
if (enableTransitionTracing) {
|
|
const instance: TracingMarkerInstance | null =
|
|
interruptedWork.stateNode;
|
|
if (instance !== null) {
|
|
popMarkerInstance(interruptedWork);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
export {unwindWork, unwindInterruptedWork};
|