mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
[refactor] Add element type for Activity (#32499)
This PR separates Activity to it's own element type separate from Offscreen. The goal is to allow us to add Activity element boundary semantics during hydration similar to Suspense semantics, without impacting the Offscreen behavior in suspended children.
This commit is contained in:
parent
99563e9173
commit
1a191701fe
|
|
@ -135,6 +135,172 @@ describe('Store component filters', () => {
|
|||
});
|
||||
|
||||
// @reactVersion >= 16.0
|
||||
it('should filter Suspense', async () => {
|
||||
const Suspense = React.Suspense;
|
||||
await actAsync(async () =>
|
||||
render(
|
||||
<React.Fragment>
|
||||
<Suspense>
|
||||
<div>Visible</div>
|
||||
</Suspense>
|
||||
<Suspense>
|
||||
<div>Hidden</div>
|
||||
</Suspense>
|
||||
</React.Fragment>,
|
||||
),
|
||||
);
|
||||
|
||||
expect(store).toMatchInlineSnapshot(`
|
||||
[root]
|
||||
▾ <Suspense>
|
||||
<div>
|
||||
▾ <Suspense>
|
||||
<div>
|
||||
`);
|
||||
|
||||
await actAsync(
|
||||
async () =>
|
||||
(store.componentFilters = [
|
||||
utils.createElementTypeFilter(Types.ElementTypeActivity),
|
||||
]),
|
||||
);
|
||||
|
||||
expect(store).toMatchInlineSnapshot(`
|
||||
[root]
|
||||
▾ <Suspense>
|
||||
<div>
|
||||
▾ <Suspense>
|
||||
<div>
|
||||
`);
|
||||
|
||||
await actAsync(
|
||||
async () =>
|
||||
(store.componentFilters = [
|
||||
utils.createElementTypeFilter(Types.ElementTypeActivity, false),
|
||||
]),
|
||||
);
|
||||
|
||||
expect(store).toMatchInlineSnapshot(`
|
||||
[root]
|
||||
▾ <Suspense>
|
||||
<div>
|
||||
▾ <Suspense>
|
||||
<div>
|
||||
`);
|
||||
});
|
||||
|
||||
it('should filter Activity', async () => {
|
||||
const Activity = React.unstable_Activity;
|
||||
|
||||
if (Activity != null) {
|
||||
await actAsync(async () =>
|
||||
render(
|
||||
<React.Fragment>
|
||||
<Activity mode="visible">
|
||||
<div>Visible</div>
|
||||
</Activity>
|
||||
<Activity mode="hidden">
|
||||
<div>Hidden</div>
|
||||
</Activity>
|
||||
</React.Fragment>,
|
||||
),
|
||||
);
|
||||
|
||||
expect(store).toMatchInlineSnapshot(`
|
||||
[root]
|
||||
▾ <Activity>
|
||||
<div>
|
||||
▾ <Activity>
|
||||
<div>
|
||||
`);
|
||||
|
||||
await actAsync(
|
||||
async () =>
|
||||
(store.componentFilters = [
|
||||
utils.createElementTypeFilter(Types.ElementTypeActivity),
|
||||
]),
|
||||
);
|
||||
|
||||
expect(store).toMatchInlineSnapshot(`
|
||||
[root]
|
||||
<div>
|
||||
<div>
|
||||
`);
|
||||
|
||||
await actAsync(
|
||||
async () =>
|
||||
(store.componentFilters = [
|
||||
utils.createElementTypeFilter(Types.ElementTypeActivity, false),
|
||||
]),
|
||||
);
|
||||
|
||||
expect(store).toMatchInlineSnapshot(`
|
||||
[root]
|
||||
▾ <Activity>
|
||||
<div>
|
||||
▾ <Activity>
|
||||
<div>
|
||||
`);
|
||||
}
|
||||
});
|
||||
|
||||
it('should filter ViewTransition', async () => {
|
||||
const ViewTransition = React.unstable_ViewTransition;
|
||||
|
||||
if (ViewTransition != null) {
|
||||
await actAsync(async () =>
|
||||
render(
|
||||
<React.Fragment>
|
||||
<ViewTransition>
|
||||
<div>Visible</div>
|
||||
</ViewTransition>
|
||||
<ViewTransition>
|
||||
<div>Hidden</div>
|
||||
</ViewTransition>
|
||||
</React.Fragment>,
|
||||
),
|
||||
);
|
||||
|
||||
expect(store).toMatchInlineSnapshot(`
|
||||
[root]
|
||||
▾ <ViewTransition>
|
||||
<div>
|
||||
▾ <ViewTransition>
|
||||
<div>
|
||||
`);
|
||||
|
||||
await actAsync(
|
||||
async () =>
|
||||
(store.componentFilters = [
|
||||
utils.createElementTypeFilter(Types.ElementTypeActivity),
|
||||
]),
|
||||
);
|
||||
|
||||
expect(store).toMatchInlineSnapshot(`
|
||||
[root]
|
||||
▾ <ViewTransition>
|
||||
<div>
|
||||
▾ <ViewTransition>
|
||||
<div>
|
||||
`);
|
||||
|
||||
await actAsync(
|
||||
async () =>
|
||||
(store.componentFilters = [
|
||||
utils.createElementTypeFilter(Types.ElementTypeActivity, false),
|
||||
]),
|
||||
);
|
||||
|
||||
expect(store).toMatchInlineSnapshot(`
|
||||
[root]
|
||||
▾ <ViewTransition>
|
||||
<div>
|
||||
▾ <ViewTransition>
|
||||
<div>
|
||||
`);
|
||||
}
|
||||
});
|
||||
|
||||
it('should ignore invalid ElementTypeRoot filter', async () => {
|
||||
const Component = () => <div>Hi</div>;
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ export function describeFiber(
|
|||
ForwardRef,
|
||||
ClassComponent,
|
||||
ViewTransitionComponent,
|
||||
ActivityComponent,
|
||||
} = workTagMap;
|
||||
|
||||
switch (workInProgress.tag) {
|
||||
|
|
@ -60,6 +61,8 @@ export function describeFiber(
|
|||
return describeBuiltInComponentFrame('SuspenseList');
|
||||
case ViewTransitionComponent:
|
||||
return describeBuiltInComponentFrame('ViewTransition');
|
||||
case ActivityComponent:
|
||||
return describeBuiltInComponentFrame('Activity');
|
||||
case FunctionComponent:
|
||||
case IndeterminateComponent:
|
||||
case SimpleMemoComponent:
|
||||
|
|
@ -154,6 +157,7 @@ export function getOwnerStackByFiberInDev(
|
|||
SuspenseComponent,
|
||||
SuspenseListComponent,
|
||||
ViewTransitionComponent,
|
||||
ActivityComponent,
|
||||
} = workTagMap;
|
||||
try {
|
||||
let info = '';
|
||||
|
|
@ -184,6 +188,9 @@ export function getOwnerStackByFiberInDev(
|
|||
case ViewTransitionComponent:
|
||||
info += describeBuiltInComponentFrame('ViewTransition');
|
||||
break;
|
||||
case ActivityComponent:
|
||||
info += describeBuiltInComponentFrame('Activity');
|
||||
break;
|
||||
}
|
||||
|
||||
let owner: void | null | Fiber | ReactComponentInfo = workInProgress;
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import {
|
|||
ElementTypeSuspenseList,
|
||||
ElementTypeTracingMarker,
|
||||
ElementTypeViewTransition,
|
||||
ElementTypeActivity,
|
||||
ElementTypeVirtual,
|
||||
StrictMode,
|
||||
} from 'react-devtools-shared/src/frontend/types';
|
||||
|
|
@ -385,6 +386,7 @@ export function getInternalReactConstants(version: string): {
|
|||
YieldComponent: -1, // Removed
|
||||
Throw: 29,
|
||||
ViewTransitionComponent: 30, // Experimental
|
||||
ActivityComponent: 31,
|
||||
};
|
||||
} else if (gte(version, '17.0.0-alpha')) {
|
||||
ReactTypeOfWork = {
|
||||
|
|
@ -421,6 +423,7 @@ export function getInternalReactConstants(version: string): {
|
|||
YieldComponent: -1, // Removed
|
||||
Throw: -1, // Doesn't exist yet
|
||||
ViewTransitionComponent: -1, // Doesn't exist yet
|
||||
ActivityComponent: -1, // Doesn't exist yet
|
||||
};
|
||||
} else if (gte(version, '16.6.0-beta.0')) {
|
||||
ReactTypeOfWork = {
|
||||
|
|
@ -457,6 +460,7 @@ export function getInternalReactConstants(version: string): {
|
|||
YieldComponent: -1, // Removed
|
||||
Throw: -1, // Doesn't exist yet
|
||||
ViewTransitionComponent: -1, // Doesn't exist yet
|
||||
ActivityComponent: -1, // Doesn't exist yet
|
||||
};
|
||||
} else if (gte(version, '16.4.3-alpha')) {
|
||||
ReactTypeOfWork = {
|
||||
|
|
@ -493,6 +497,7 @@ export function getInternalReactConstants(version: string): {
|
|||
YieldComponent: -1, // Removed
|
||||
Throw: -1, // Doesn't exist yet
|
||||
ViewTransitionComponent: -1, // Doesn't exist yet
|
||||
ActivityComponent: -1, // Doesn't exist yet
|
||||
};
|
||||
} else {
|
||||
ReactTypeOfWork = {
|
||||
|
|
@ -529,6 +534,7 @@ export function getInternalReactConstants(version: string): {
|
|||
YieldComponent: 9,
|
||||
Throw: -1, // Doesn't exist yet
|
||||
ViewTransitionComponent: -1, // Doesn't exist yet
|
||||
ActivityComponent: -1, // Doesn't exist yet
|
||||
};
|
||||
}
|
||||
// **********************************************************
|
||||
|
|
@ -572,6 +578,7 @@ export function getInternalReactConstants(version: string): {
|
|||
TracingMarkerComponent,
|
||||
Throw,
|
||||
ViewTransitionComponent,
|
||||
ActivityComponent,
|
||||
} = ReactTypeOfWork;
|
||||
|
||||
function resolveFiberType(type: any): $FlowFixMe {
|
||||
|
|
@ -622,6 +629,8 @@ export function getInternalReactConstants(version: string): {
|
|||
}
|
||||
|
||||
switch (tag) {
|
||||
case ActivityComponent:
|
||||
return 'Activity';
|
||||
case CacheComponent:
|
||||
return 'Cache';
|
||||
case ClassComponent:
|
||||
|
|
@ -892,6 +901,7 @@ export function attach(
|
|||
StrictModeBits,
|
||||
} = getInternalReactConstants(version);
|
||||
const {
|
||||
ActivityComponent,
|
||||
CacheComponent,
|
||||
ClassComponent,
|
||||
ContextConsumer,
|
||||
|
|
@ -1565,6 +1575,8 @@ export function attach(
|
|||
const {type, tag} = fiber;
|
||||
|
||||
switch (tag) {
|
||||
case ActivityComponent:
|
||||
return ElementTypeActivity;
|
||||
case ClassComponent:
|
||||
case IncompleteClassComponent:
|
||||
return ElementTypeClass;
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ export type WorkTagMap = {
|
|||
YieldComponent: WorkTag,
|
||||
Throw: WorkTag,
|
||||
ViewTransitionComponent: WorkTag,
|
||||
ActivityComponent: WorkTag,
|
||||
};
|
||||
|
||||
export type HostInstance = Object;
|
||||
|
|
|
|||
|
|
@ -472,6 +472,8 @@ export default function ComponentsSettings({
|
|||
((parseInt(currentTarget.value, 10): any): ElementType),
|
||||
)
|
||||
}>
|
||||
{/* TODO: currently only experimental, only list this if it's available */}
|
||||
{/*<option value={ElementTypeActivity}>activity</option>*/}
|
||||
<option value={ElementTypeClass}>class</option>
|
||||
<option value={ElementTypeContext}>context</option>
|
||||
<option value={ElementTypeFunction}>function</option>
|
||||
|
|
@ -485,6 +487,10 @@ export default function ComponentsSettings({
|
|||
<option value={ElementTypeOtherOrUnknown}>other</option>
|
||||
<option value={ElementTypeProfiler}>profiler</option>
|
||||
<option value={ElementTypeSuspense}>suspense</option>
|
||||
{/* TODO: currently only experimental, only list this if it's available */}
|
||||
{/*<option value={ElementTypeViewTransition}>*/}
|
||||
{/* view transition*/}
|
||||
{/*</option>*/}
|
||||
</select>
|
||||
)}
|
||||
{(componentFilter.type === ComponentFilterLocation ||
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ export const ElementTypeSuspenseList = 13;
|
|||
export const ElementTypeTracingMarker = 14;
|
||||
export const ElementTypeVirtual = 15;
|
||||
export const ElementTypeViewTransition = 16;
|
||||
export const ElementTypeActivity = 17;
|
||||
|
||||
// Different types of elements displayed in the Elements tree.
|
||||
// These types may be used to visually distinguish types,
|
||||
|
|
@ -68,7 +69,8 @@ export type ElementType =
|
|||
| 13
|
||||
| 14
|
||||
| 15
|
||||
| 16;
|
||||
| 16
|
||||
| 17;
|
||||
|
||||
// WARNING
|
||||
// The values below are referenced by ComponentFilters (which are saved via localStorage).
|
||||
|
|
|
|||
15
packages/react-reconciler/src/ReactFiber.js
vendored
15
packages/react-reconciler/src/ReactFiber.js
vendored
|
|
@ -71,6 +71,7 @@ import {
|
|||
TracingMarkerComponent,
|
||||
Throw,
|
||||
ViewTransitionComponent,
|
||||
ActivityComponent,
|
||||
} from './ReactWorkTags';
|
||||
import {OffscreenVisible} from './ReactFiberActivityComponent';
|
||||
import {getComponentNameFromOwner} from 'react-reconciler/src/getComponentNameFromFiber';
|
||||
|
|
@ -107,6 +108,7 @@ import {
|
|||
REACT_TRACING_MARKER_TYPE,
|
||||
REACT_ELEMENT_TYPE,
|
||||
REACT_VIEW_TRANSITION_TYPE,
|
||||
REACT_ACTIVITY_TYPE,
|
||||
} from 'shared/ReactSymbols';
|
||||
import {TransitionTracingMarker} from './ReactFiberTracingMarkerComponent';
|
||||
import {
|
||||
|
|
@ -588,6 +590,8 @@ export function createFiberFromTypeAndProps(
|
|||
}
|
||||
} else {
|
||||
getTag: switch (type) {
|
||||
case REACT_ACTIVITY_TYPE:
|
||||
return createFiberFromActivity(pendingProps, mode, lanes, key);
|
||||
case REACT_FRAGMENT_TYPE:
|
||||
return createFiberFromFragment(pendingProps.children, mode, lanes, key);
|
||||
case REACT_STRICT_MODE_TYPE:
|
||||
|
|
@ -865,6 +869,17 @@ export function createFiberFromOffscreen(
|
|||
fiber.stateNode = primaryChildInstance;
|
||||
return fiber;
|
||||
}
|
||||
export function createFiberFromActivity(
|
||||
pendingProps: OffscreenProps,
|
||||
mode: TypeOfMode,
|
||||
lanes: Lanes,
|
||||
key: null | string,
|
||||
): Fiber {
|
||||
const fiber = createFiber(ActivityComponent, pendingProps, key, mode);
|
||||
fiber.elementType = REACT_ACTIVITY_TYPE;
|
||||
fiber.lanes = lanes;
|
||||
return fiber;
|
||||
}
|
||||
|
||||
export function createFiberFromViewTransition(
|
||||
pendingProps: ViewTransitionProps,
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ import {
|
|||
TracingMarkerComponent,
|
||||
Throw,
|
||||
ViewTransitionComponent,
|
||||
ActivityComponent,
|
||||
} from './ReactWorkTags';
|
||||
import {
|
||||
NoFlags,
|
||||
|
|
@ -867,6 +868,46 @@ function deferHiddenOffscreenComponent(
|
|||
// fork the function.
|
||||
const updateLegacyHiddenComponent = updateOffscreenComponent;
|
||||
|
||||
function updateActivityComponent(
|
||||
current: null | Fiber,
|
||||
workInProgress: Fiber,
|
||||
renderLanes: Lanes,
|
||||
) {
|
||||
const nextProps = workInProgress.pendingProps;
|
||||
const nextChildren = nextProps.children;
|
||||
const nextMode = nextProps.mode;
|
||||
const mode = workInProgress.mode;
|
||||
const offscreenChildProps: OffscreenProps = {
|
||||
mode: nextMode,
|
||||
children: nextChildren,
|
||||
};
|
||||
|
||||
if (current === null) {
|
||||
const primaryChildFragment = mountWorkInProgressOffscreenFiber(
|
||||
offscreenChildProps,
|
||||
mode,
|
||||
renderLanes,
|
||||
);
|
||||
primaryChildFragment.ref = workInProgress.ref;
|
||||
workInProgress.child = primaryChildFragment;
|
||||
primaryChildFragment.return = workInProgress;
|
||||
|
||||
return primaryChildFragment;
|
||||
} else {
|
||||
const currentChild: Fiber = (current.child: any);
|
||||
|
||||
const primaryChildFragment = updateWorkInProgressOffscreenFiber(
|
||||
currentChild,
|
||||
offscreenChildProps,
|
||||
);
|
||||
|
||||
primaryChildFragment.ref = workInProgress.ref;
|
||||
workInProgress.child = primaryChildFragment;
|
||||
primaryChildFragment.return = workInProgress;
|
||||
return primaryChildFragment;
|
||||
}
|
||||
}
|
||||
|
||||
function updateCacheComponent(
|
||||
current: Fiber | null,
|
||||
workInProgress: Fiber,
|
||||
|
|
@ -4025,6 +4066,9 @@ function beginWork(
|
|||
}
|
||||
break;
|
||||
}
|
||||
case ActivityComponent: {
|
||||
return updateActivityComponent(current, workInProgress, renderLanes);
|
||||
}
|
||||
case OffscreenComponent: {
|
||||
return updateOffscreenComponent(current, workInProgress, renderLanes);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ import {
|
|||
TracingMarkerComponent,
|
||||
Throw,
|
||||
ViewTransitionComponent,
|
||||
ActivityComponent,
|
||||
} from './ReactWorkTags';
|
||||
import {NoMode, ConcurrentMode, ProfileMode} from './ReactTypeOfMode';
|
||||
import {
|
||||
|
|
@ -972,6 +973,7 @@ function completeWork(
|
|||
}
|
||||
// Fallthrough
|
||||
}
|
||||
case ActivityComponent:
|
||||
case LazyComponent:
|
||||
case SimpleMemoComponent:
|
||||
case FunctionComponent:
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import {
|
|||
ClassComponent,
|
||||
HostText,
|
||||
ViewTransitionComponent,
|
||||
ActivityComponent,
|
||||
} from './ReactWorkTags';
|
||||
import {
|
||||
describeBuiltInComponentFrame,
|
||||
|
|
@ -53,6 +54,8 @@ function describeFiber(fiber: Fiber): string {
|
|||
return describeFunctionComponentFrame(fiber.type.render);
|
||||
case ClassComponent:
|
||||
return describeClassComponentFrame(fiber.type);
|
||||
case ActivityComponent:
|
||||
return describeBuiltInComponentFrame('Activity');
|
||||
case ViewTransitionComponent:
|
||||
if (enableViewTransition) {
|
||||
return describeBuiltInComponentFrame('ViewTransition');
|
||||
|
|
@ -129,9 +132,12 @@ export function getOwnerStackByFiberInDev(workInProgress: Fiber): string {
|
|||
case SuspenseListComponent:
|
||||
info += describeBuiltInComponentFrame('SuspenseList');
|
||||
break;
|
||||
case ActivityComponent:
|
||||
info += describeBuiltInComponentFrame('Activity');
|
||||
break;
|
||||
case ViewTransitionComponent:
|
||||
if (enableViewTransition) {
|
||||
info += describeBuiltInComponentFrame('SuspenseList');
|
||||
info += describeBuiltInComponentFrame('ViewTransition');
|
||||
break;
|
||||
}
|
||||
// Fallthrough
|
||||
|
|
|
|||
|
|
@ -38,7 +38,8 @@ export type WorkTag =
|
|||
| 27
|
||||
| 28
|
||||
| 29
|
||||
| 30;
|
||||
| 30
|
||||
| 31;
|
||||
|
||||
export const FunctionComponent = 0;
|
||||
export const ClassComponent = 1;
|
||||
|
|
@ -69,3 +70,4 @@ export const HostSingleton = 27;
|
|||
export const IncompleteFunctionComponent = 28;
|
||||
export const Throw = 29;
|
||||
export const ViewTransitionComponent = 30;
|
||||
export const ActivityComponent = 31;
|
||||
|
|
|
|||
|
|
@ -10,17 +10,72 @@
|
|||
'use strict';
|
||||
|
||||
let React;
|
||||
let Suspense;
|
||||
let Activity;
|
||||
let ViewTransition;
|
||||
let ReactNoop;
|
||||
let waitForAll;
|
||||
|
||||
describe('ReactFragment', () => {
|
||||
let didCatchErrors = [];
|
||||
let rootCaughtErrors = [];
|
||||
let SomethingThatErrors;
|
||||
let CatchingBoundary;
|
||||
let onCaughtError;
|
||||
|
||||
beforeEach(function () {
|
||||
jest.resetModules();
|
||||
|
||||
React = require('react');
|
||||
Suspense = React.Suspense;
|
||||
Activity = React.unstable_Activity;
|
||||
ViewTransition = React.unstable_ViewTransition;
|
||||
ReactNoop = require('react-noop-renderer');
|
||||
const InternalTestUtils = require('internal-test-utils');
|
||||
waitForAll = InternalTestUtils.waitForAll;
|
||||
|
||||
didCatchErrors = [];
|
||||
rootCaughtErrors = [];
|
||||
|
||||
onCaughtError = function (error, errorInfo) {
|
||||
rootCaughtErrors.push(
|
||||
error.message,
|
||||
normalizeCodeLocInfo(errorInfo.componentStack),
|
||||
React.captureOwnerStack
|
||||
? normalizeCodeLocInfo(React.captureOwnerStack())
|
||||
: null,
|
||||
);
|
||||
};
|
||||
|
||||
SomethingThatErrors = () => {
|
||||
throw new Error('uh oh');
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-shadow
|
||||
CatchingBoundary = class CatchingBoundary extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error) {
|
||||
return {errored: true};
|
||||
}
|
||||
|
||||
componentDidCatch(err, errInfo) {
|
||||
didCatchErrors.push(
|
||||
err.message,
|
||||
normalizeCodeLocInfo(errInfo.componentStack),
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.errored) {
|
||||
return null;
|
||||
}
|
||||
return this.props.children;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
function componentStack(components) {
|
||||
|
|
@ -38,21 +93,7 @@ describe('ReactFragment', () => {
|
|||
);
|
||||
}
|
||||
|
||||
it('retains component stacks when rethrowing an error', async () => {
|
||||
function Foo() {
|
||||
return (
|
||||
<RethrowingBoundary>
|
||||
<Bar />
|
||||
</RethrowingBoundary>
|
||||
);
|
||||
}
|
||||
function Bar() {
|
||||
return <SomethingThatErrors />;
|
||||
}
|
||||
function SomethingThatErrors() {
|
||||
throw new Error('uh oh');
|
||||
}
|
||||
|
||||
it('retains component and owner stacks when rethrowing an error', async () => {
|
||||
class RethrowingBoundary extends React.Component {
|
||||
static getDerivedStateFromError(error) {
|
||||
throw error;
|
||||
|
|
@ -63,33 +104,26 @@ describe('ReactFragment', () => {
|
|||
}
|
||||
}
|
||||
|
||||
const errors = [];
|
||||
class CatchingBoundary extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {};
|
||||
}
|
||||
static getDerivedStateFromError(error) {
|
||||
return {errored: true};
|
||||
}
|
||||
componentDidCatch(err, errInfo) {
|
||||
errors.push(err.message, normalizeCodeLocInfo(errInfo.componentStack));
|
||||
}
|
||||
render() {
|
||||
if (this.state.errored) {
|
||||
return null;
|
||||
}
|
||||
return this.props.children;
|
||||
function Foo() {
|
||||
return (
|
||||
<RethrowingBoundary>
|
||||
<Bar />
|
||||
</RethrowingBoundary>
|
||||
);
|
||||
}
|
||||
function Bar() {
|
||||
return <SomethingThatErrors />;
|
||||
}
|
||||
|
||||
ReactNoop.render(
|
||||
ReactNoop.createRoot({
|
||||
onCaughtError,
|
||||
}).render(
|
||||
<CatchingBoundary>
|
||||
<Foo />
|
||||
</CatchingBoundary>,
|
||||
);
|
||||
await waitForAll([]);
|
||||
expect(errors).toEqual([
|
||||
expect(didCatchErrors).toEqual([
|
||||
'uh oh',
|
||||
componentStack([
|
||||
'SomethingThatErrors',
|
||||
|
|
@ -99,67 +133,7 @@ describe('ReactFragment', () => {
|
|||
'CatchingBoundary',
|
||||
]),
|
||||
]);
|
||||
});
|
||||
|
||||
it('retains owner stacks when rethrowing an error', async () => {
|
||||
function Foo() {
|
||||
return (
|
||||
<RethrowingBoundary>
|
||||
<Bar />
|
||||
</RethrowingBoundary>
|
||||
);
|
||||
}
|
||||
function Bar() {
|
||||
return <SomethingThatErrors />;
|
||||
}
|
||||
function SomethingThatErrors() {
|
||||
throw new Error('uh oh');
|
||||
}
|
||||
|
||||
class RethrowingBoundary extends React.Component {
|
||||
static getDerivedStateFromError(error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
render() {
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
const errors = [];
|
||||
class CatchingBoundary extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {};
|
||||
}
|
||||
static getDerivedStateFromError(error) {
|
||||
return {errored: true};
|
||||
}
|
||||
render() {
|
||||
if (this.state.errored) {
|
||||
return null;
|
||||
}
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
ReactNoop.createRoot({
|
||||
onCaughtError(error, errorInfo) {
|
||||
errors.push(
|
||||
error.message,
|
||||
normalizeCodeLocInfo(errorInfo.componentStack),
|
||||
React.captureOwnerStack
|
||||
? normalizeCodeLocInfo(React.captureOwnerStack())
|
||||
: null,
|
||||
);
|
||||
},
|
||||
}).render(
|
||||
<CatchingBoundary>
|
||||
<Foo />
|
||||
</CatchingBoundary>,
|
||||
);
|
||||
await waitForAll([]);
|
||||
expect(errors).toEqual([
|
||||
expect(rootCaughtErrors).toEqual([
|
||||
'uh oh',
|
||||
componentStack([
|
||||
'SomethingThatErrors',
|
||||
|
|
@ -171,4 +145,168 @@ describe('ReactFragment', () => {
|
|||
__DEV__ ? componentStack(['Bar', 'Foo']) : null,
|
||||
]);
|
||||
});
|
||||
|
||||
it('includes built-in for Suspense', async () => {
|
||||
ReactNoop.createRoot({
|
||||
onCaughtError,
|
||||
}).render(
|
||||
<CatchingBoundary>
|
||||
<Suspense>
|
||||
<SomethingThatErrors />
|
||||
</Suspense>
|
||||
</CatchingBoundary>,
|
||||
);
|
||||
await waitForAll([]);
|
||||
expect(didCatchErrors).toEqual([
|
||||
'uh oh',
|
||||
componentStack(['SomethingThatErrors', 'Suspense', 'CatchingBoundary']),
|
||||
]);
|
||||
expect(rootCaughtErrors).toEqual([
|
||||
'uh oh',
|
||||
componentStack(['SomethingThatErrors', 'Suspense', 'CatchingBoundary']),
|
||||
__DEV__ ? componentStack(['SomethingThatErrors']) : null,
|
||||
]);
|
||||
});
|
||||
|
||||
// @gate enableActivity
|
||||
it('includes built-in for Activity', async () => {
|
||||
ReactNoop.createRoot({
|
||||
onCaughtError,
|
||||
}).render(
|
||||
<CatchingBoundary>
|
||||
<Activity>
|
||||
<SomethingThatErrors />
|
||||
</Activity>
|
||||
</CatchingBoundary>,
|
||||
);
|
||||
await waitForAll([]);
|
||||
expect(didCatchErrors).toEqual([
|
||||
'uh oh',
|
||||
componentStack(['SomethingThatErrors', 'Activity', 'CatchingBoundary']),
|
||||
]);
|
||||
expect(rootCaughtErrors).toEqual([
|
||||
'uh oh',
|
||||
componentStack(['SomethingThatErrors', 'Activity', 'CatchingBoundary']),
|
||||
__DEV__ ? componentStack(['SomethingThatErrors']) : null,
|
||||
]);
|
||||
});
|
||||
|
||||
// @gate enableViewTransition
|
||||
it('includes built-in for ViewTransition', async () => {
|
||||
ReactNoop.createRoot({
|
||||
onCaughtError,
|
||||
}).render(
|
||||
<CatchingBoundary>
|
||||
<ViewTransition>
|
||||
<SomethingThatErrors />
|
||||
</ViewTransition>
|
||||
</CatchingBoundary>,
|
||||
);
|
||||
await waitForAll([]);
|
||||
expect(didCatchErrors).toEqual([
|
||||
'uh oh',
|
||||
componentStack([
|
||||
'SomethingThatErrors',
|
||||
'ViewTransition',
|
||||
'CatchingBoundary',
|
||||
]),
|
||||
]);
|
||||
expect(rootCaughtErrors).toEqual([
|
||||
'uh oh',
|
||||
componentStack([
|
||||
'SomethingThatErrors',
|
||||
'ViewTransition',
|
||||
'CatchingBoundary',
|
||||
]),
|
||||
__DEV__ ? componentStack(['SomethingThatErrors']) : null,
|
||||
]);
|
||||
});
|
||||
|
||||
it('includes built-in for Lazy', async () => {
|
||||
// Lazy component throws
|
||||
const LazyComponent = React.lazy(() => {
|
||||
throw new Error('uh oh');
|
||||
});
|
||||
|
||||
ReactNoop.createRoot({
|
||||
onCaughtError,
|
||||
}).render(
|
||||
<CatchingBoundary>
|
||||
<LazyComponent />
|
||||
</CatchingBoundary>,
|
||||
);
|
||||
await waitForAll([]);
|
||||
expect(didCatchErrors).toEqual([
|
||||
'uh oh',
|
||||
componentStack(['Lazy', 'CatchingBoundary']),
|
||||
]);
|
||||
expect(rootCaughtErrors).toEqual([
|
||||
'uh oh',
|
||||
componentStack(['Lazy', 'CatchingBoundary']),
|
||||
__DEV__ ? '' : null, // No owner stack
|
||||
]);
|
||||
});
|
||||
|
||||
// @gate enableSuspenseList
|
||||
it('includes built-in for SuspenseList', async () => {
|
||||
const SuspenseList = React.unstable_SuspenseList;
|
||||
|
||||
ReactNoop.createRoot({
|
||||
onCaughtError,
|
||||
}).render(
|
||||
<CatchingBoundary>
|
||||
<SuspenseList>
|
||||
<SomethingThatErrors />
|
||||
</SuspenseList>
|
||||
</CatchingBoundary>,
|
||||
);
|
||||
await waitForAll([]);
|
||||
expect(didCatchErrors).toEqual([
|
||||
'uh oh',
|
||||
componentStack([
|
||||
'SomethingThatErrors',
|
||||
'SuspenseList',
|
||||
'CatchingBoundary',
|
||||
]),
|
||||
]);
|
||||
expect(rootCaughtErrors).toEqual([
|
||||
'uh oh',
|
||||
componentStack([
|
||||
'SomethingThatErrors',
|
||||
'SuspenseList',
|
||||
'CatchingBoundary',
|
||||
]),
|
||||
__DEV__ ? componentStack(['SomethingThatErrors']) : null,
|
||||
]);
|
||||
});
|
||||
|
||||
it('does not include built-in for Fragment', async () => {
|
||||
ReactNoop.createRoot({
|
||||
onCaughtError,
|
||||
}).render(
|
||||
<CatchingBoundary>
|
||||
<>
|
||||
<SomethingThatErrors />
|
||||
</>
|
||||
</CatchingBoundary>,
|
||||
);
|
||||
await waitForAll([]);
|
||||
expect(didCatchErrors).toEqual([
|
||||
'uh oh',
|
||||
componentStack([
|
||||
'SomethingThatErrors',
|
||||
// No Fragment
|
||||
'CatchingBoundary',
|
||||
]),
|
||||
]);
|
||||
expect(rootCaughtErrors).toEqual([
|
||||
'uh oh',
|
||||
componentStack([
|
||||
'SomethingThatErrors',
|
||||
// No Fragment
|
||||
'CatchingBoundary',
|
||||
]),
|
||||
__DEV__ ? componentStack(['SomethingThatErrors']) : null,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ import {
|
|||
TracingMarkerComponent,
|
||||
Throw,
|
||||
ViewTransitionComponent,
|
||||
ActivityComponent,
|
||||
} from 'react-reconciler/src/ReactWorkTags';
|
||||
import getComponentNameFromType from 'shared/getComponentNameFromType';
|
||||
import {REACT_STRICT_MODE_TYPE} from 'shared/ReactSymbols';
|
||||
|
|
@ -85,6 +86,8 @@ export function getComponentNameFromOwner(
|
|||
export default function getComponentNameFromFiber(fiber: Fiber): string | null {
|
||||
const {tag, type} = fiber;
|
||||
switch (tag) {
|
||||
case ActivityComponent:
|
||||
return 'Activity';
|
||||
case CacheComponent:
|
||||
return 'Cache';
|
||||
case ContextConsumer:
|
||||
|
|
|
|||
4
packages/react-server/src/ReactFizzServer.js
vendored
4
packages/react-server/src/ReactFizzServer.js
vendored
|
|
@ -151,9 +151,9 @@ import {
|
|||
REACT_CONTEXT_TYPE,
|
||||
REACT_CONSUMER_TYPE,
|
||||
REACT_SCOPE_TYPE,
|
||||
REACT_OFFSCREEN_TYPE,
|
||||
REACT_POSTPONE_TYPE,
|
||||
REACT_VIEW_TRANSITION_TYPE,
|
||||
REACT_ACTIVITY_TYPE,
|
||||
} from 'shared/ReactSymbols';
|
||||
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||
import {
|
||||
|
|
@ -2253,7 +2253,7 @@ function renderElement(
|
|||
task.keyPath = prevKeyPath;
|
||||
return;
|
||||
}
|
||||
case REACT_OFFSCREEN_TYPE: {
|
||||
case REACT_ACTIVITY_TYPE: {
|
||||
renderOffscreen(request, task, keyPath, props);
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import {
|
|||
REACT_SUSPENSE_TYPE,
|
||||
REACT_SUSPENSE_LIST_TYPE,
|
||||
REACT_LEGACY_HIDDEN_TYPE,
|
||||
REACT_OFFSCREEN_TYPE,
|
||||
REACT_ACTIVITY_TYPE,
|
||||
REACT_SCOPE_TYPE,
|
||||
REACT_TRACING_MARKER_TYPE,
|
||||
REACT_VIEW_TRANSITION_TYPE,
|
||||
|
|
@ -116,7 +116,7 @@ export {
|
|||
useDeferredValue,
|
||||
REACT_SUSPENSE_LIST_TYPE as unstable_SuspenseList,
|
||||
REACT_LEGACY_HIDDEN_TYPE as unstable_LegacyHidden,
|
||||
REACT_OFFSCREEN_TYPE as unstable_Activity,
|
||||
REACT_ACTIVITY_TYPE as unstable_Activity,
|
||||
getCacheForType as unstable_getCacheForType,
|
||||
useCacheRefresh as unstable_useCacheRefresh,
|
||||
use,
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ export const REACT_MEMO_TYPE: symbol = Symbol.for('react.memo');
|
|||
export const REACT_LAZY_TYPE: symbol = Symbol.for('react.lazy');
|
||||
export const REACT_SCOPE_TYPE: symbol = Symbol.for('react.scope');
|
||||
export const REACT_OFFSCREEN_TYPE: symbol = Symbol.for('react.offscreen');
|
||||
export const REACT_ACTIVITY_TYPE: symbol = Symbol.for('react.activity');
|
||||
export const REACT_LEGACY_HIDDEN_TYPE: symbol = Symbol.for(
|
||||
'react.legacy_hidden',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import {
|
|||
REACT_LAZY_TYPE,
|
||||
REACT_TRACING_MARKER_TYPE,
|
||||
REACT_VIEW_TRANSITION_TYPE,
|
||||
REACT_ACTIVITY_TYPE,
|
||||
} from 'shared/ReactSymbols';
|
||||
|
||||
import {
|
||||
|
|
@ -83,7 +84,8 @@ export default function getComponentNameFromType(type: mixed): string | null {
|
|||
return 'Suspense';
|
||||
case REACT_SUSPENSE_LIST_TYPE:
|
||||
return 'SuspenseList';
|
||||
// Fall through
|
||||
case REACT_ACTIVITY_TYPE:
|
||||
return 'Activity';
|
||||
case REACT_VIEW_TRANSITION_TYPE:
|
||||
if (enableViewTransition) {
|
||||
return 'ViewTransition';
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import {
|
|||
REACT_OFFSCREEN_TYPE,
|
||||
REACT_TRACING_MARKER_TYPE,
|
||||
REACT_VIEW_TRANSITION_TYPE,
|
||||
REACT_ACTIVITY_TYPE,
|
||||
} from 'shared/ReactSymbols';
|
||||
import {
|
||||
enableScopeAPI,
|
||||
|
|
@ -50,6 +51,7 @@ export default function isValidElementType(type: mixed): boolean {
|
|||
type === REACT_SUSPENSE_TYPE ||
|
||||
type === REACT_SUSPENSE_LIST_TYPE ||
|
||||
(enableLegacyHidden && type === REACT_LEGACY_HIDDEN_TYPE) ||
|
||||
type === REACT_ACTIVITY_TYPE ||
|
||||
type === REACT_OFFSCREEN_TYPE ||
|
||||
(enableScopeAPI && type === REACT_SCOPE_TYPE) ||
|
||||
(enableTransitionTracing && type === REACT_TRACING_MARKER_TYPE) ||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user