mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Track Owner for Server Components in DEV (#28753)
This implements the concept of a DEV-only "owner" for Server Components. The owner concept isn't really super useful. We barely use it anymore, but we do have it as a concept in DevTools in a couple of cases so this adds it for parity. However, this is mainly interesting because it could be used to wire up future owner-based stacks. I do this by outlining the DebugInfo for a Server Component (ReactComponentInfo). Then I just rely on Flight deduping to refer to that. I refer to the same thing by referential equality so that we can associate a Server Component parent in DebugInfo with an owner. If you suspend and replay a Server Component, we have to restore the same owner. To do that, I did a little ugly hack and stashed it on the thenable state object. Felt unnecessarily complicated to add a stateful wrapper for this one dev-only case. The owner could really be anything since it could be coming from a different implementation. Because this is the first time we have an owner other than Fiber, I have to fix up a bunch of places that assumes Fiber. I mainly did the `typeof owner.tag === 'number'` to assume it's a Fiber for now. This also doesn't actually add it to DevTools / RN Inspector yet. I just ignore them there for now. Because Server Components can be async the owner isn't tracked after an await. We need per-component AsyncLocalStorage for that. This can be done in a follow up.
This commit is contained in:
committed by
Rick Hanlon
parent
ef3730f4bd
commit
ab711223d2
+18
-7
@@ -484,6 +484,7 @@ function createElement(
|
||||
type: mixed,
|
||||
key: mixed,
|
||||
props: mixed,
|
||||
owner: null | ReactComponentInfo, // DEV-only
|
||||
): React$Element<any> {
|
||||
let element: any;
|
||||
if (__DEV__ && enableRefAsProp) {
|
||||
@@ -493,7 +494,7 @@ function createElement(
|
||||
type,
|
||||
key,
|
||||
props,
|
||||
_owner: null,
|
||||
_owner: owner,
|
||||
}: any);
|
||||
Object.defineProperty(element, 'ref', {
|
||||
enumerable: false,
|
||||
@@ -520,7 +521,7 @@ function createElement(
|
||||
props,
|
||||
|
||||
// Record the component responsible for creating this element.
|
||||
_owner: null,
|
||||
_owner: owner,
|
||||
}: any);
|
||||
}
|
||||
|
||||
@@ -854,7 +855,12 @@ function parseModelTuple(
|
||||
if (tuple[0] === REACT_ELEMENT_TYPE) {
|
||||
// TODO: Consider having React just directly accept these arrays as elements.
|
||||
// Or even change the ReactElement type to be an array.
|
||||
return createElement(tuple[1], tuple[2], tuple[3]);
|
||||
return createElement(
|
||||
tuple[1],
|
||||
tuple[2],
|
||||
tuple[3],
|
||||
__DEV__ ? (tuple: any)[4] : null,
|
||||
);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
@@ -1132,12 +1138,14 @@ function resolveConsoleEntry(
|
||||
);
|
||||
}
|
||||
|
||||
const payload: [string, string, string, mixed] = parseModel(response, value);
|
||||
const payload: [string, string, null | ReactComponentInfo, string, mixed] =
|
||||
parseModel(response, value);
|
||||
const methodName = payload[0];
|
||||
// TODO: Restore the fake stack before logging.
|
||||
// const stackTrace = payload[1];
|
||||
const env = payload[2];
|
||||
const args = payload.slice(3);
|
||||
// const owner = payload[2];
|
||||
const env = payload[3];
|
||||
const args = payload.slice(4);
|
||||
printToConsole(methodName, args, env);
|
||||
}
|
||||
|
||||
@@ -1286,7 +1294,10 @@ function processFullRow(
|
||||
}
|
||||
case 68 /* "D" */: {
|
||||
if (__DEV__) {
|
||||
const debugInfo = JSON.parse(row);
|
||||
const debugInfo: ReactComponentInfo | ReactAsyncInfo = parseModel(
|
||||
response,
|
||||
row,
|
||||
);
|
||||
resolveDebugInfo(response, id, debugInfo);
|
||||
return;
|
||||
}
|
||||
|
||||
+53
-5
@@ -214,7 +214,7 @@ describe('ReactFlight', () => {
|
||||
const rootModel = await ReactNoopFlightClient.read(transport);
|
||||
const greeting = rootModel.greeting;
|
||||
expect(greeting._debugInfo).toEqual(
|
||||
__DEV__ ? [{name: 'Greeting', env: 'Server'}] : undefined,
|
||||
__DEV__ ? [{name: 'Greeting', env: 'Server', owner: null}] : undefined,
|
||||
);
|
||||
ReactNoop.render(greeting);
|
||||
});
|
||||
@@ -241,7 +241,7 @@ describe('ReactFlight', () => {
|
||||
await act(async () => {
|
||||
const promise = ReactNoopFlightClient.read(transport);
|
||||
expect(promise._debugInfo).toEqual(
|
||||
__DEV__ ? [{name: 'Greeting', env: 'Server'}] : undefined,
|
||||
__DEV__ ? [{name: 'Greeting', env: 'Server', owner: null}] : undefined,
|
||||
);
|
||||
ReactNoop.render(await promise);
|
||||
});
|
||||
@@ -2072,19 +2072,21 @@ describe('ReactFlight', () => {
|
||||
await act(async () => {
|
||||
const promise = ReactNoopFlightClient.read(transport);
|
||||
expect(promise._debugInfo).toEqual(
|
||||
__DEV__ ? [{name: 'ServerComponent', env: 'Server'}] : undefined,
|
||||
__DEV__
|
||||
? [{name: 'ServerComponent', env: 'Server', owner: null}]
|
||||
: undefined,
|
||||
);
|
||||
const result = await promise;
|
||||
const thirdPartyChildren = await result.props.children[1];
|
||||
// We expect the debug info to be transferred from the inner stream to the outer.
|
||||
expect(thirdPartyChildren[0]._debugInfo).toEqual(
|
||||
__DEV__
|
||||
? [{name: 'ThirdPartyComponent', env: 'third-party'}]
|
||||
? [{name: 'ThirdPartyComponent', env: 'third-party', owner: null}]
|
||||
: undefined,
|
||||
);
|
||||
expect(thirdPartyChildren[1]._debugInfo).toEqual(
|
||||
__DEV__
|
||||
? [{name: 'ThirdPartyLazyComponent', env: 'third-party'}]
|
||||
? [{name: 'ThirdPartyLazyComponent', env: 'third-party', owner: null}]
|
||||
: undefined,
|
||||
);
|
||||
ReactNoop.render(result);
|
||||
@@ -2145,4 +2147,50 @@ describe('ReactFlight', () => {
|
||||
expect(loggedFn).not.toBe(foo);
|
||||
expect(loggedFn.toString()).toBe(foo.toString());
|
||||
});
|
||||
|
||||
it('uses the server component debug info as the element owner in DEV', async () => {
|
||||
function Container({children}) {
|
||||
return children;
|
||||
}
|
||||
|
||||
function Greeting({firstName}) {
|
||||
// We can't use JSX here because it'll use the Client React.
|
||||
return ReactServer.createElement(
|
||||
Container,
|
||||
null,
|
||||
ReactServer.createElement('span', null, 'Hello, ', firstName),
|
||||
);
|
||||
}
|
||||
|
||||
const model = {
|
||||
greeting: ReactServer.createElement(Greeting, {firstName: 'Seb'}),
|
||||
};
|
||||
|
||||
const transport = ReactNoopFlightServer.render(model);
|
||||
|
||||
await act(async () => {
|
||||
const rootModel = await ReactNoopFlightClient.read(transport);
|
||||
const greeting = rootModel.greeting;
|
||||
// We've rendered down to the span.
|
||||
expect(greeting.type).toBe('span');
|
||||
if (__DEV__) {
|
||||
const greetInfo = {name: 'Greeting', env: 'Server', owner: null};
|
||||
expect(greeting._debugInfo).toEqual([
|
||||
greetInfo,
|
||||
{name: 'Container', env: 'Server', owner: greetInfo},
|
||||
]);
|
||||
// The owner that created the span was the outer server component.
|
||||
// We expect the debug info to be referentially equal to the owner.
|
||||
expect(greeting._owner).toBe(greeting._debugInfo[0]);
|
||||
} else {
|
||||
expect(greeting._debugInfo).toBe(undefined);
|
||||
expect(greeting._owner).toBe(
|
||||
gate(flags => flags.disableStringRefs) ? undefined : null,
|
||||
);
|
||||
}
|
||||
ReactNoop.render(greeting);
|
||||
});
|
||||
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span>Hello, Seb</span>);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -101,6 +101,7 @@ describe('component stack', () => {
|
||||
{
|
||||
name: 'ServerComponent',
|
||||
env: 'Server',
|
||||
owner: null,
|
||||
},
|
||||
];
|
||||
const Parent = () => ChildPromise;
|
||||
|
||||
@@ -33,10 +33,7 @@ import {
|
||||
import {disableLogs, reenableLogs} from './DevToolsConsolePatching';
|
||||
|
||||
let prefix;
|
||||
export function describeBuiltInComponentFrame(
|
||||
name: string,
|
||||
ownerFn: void | null | Function,
|
||||
): string {
|
||||
export function describeBuiltInComponentFrame(name: string): string {
|
||||
if (prefix === undefined) {
|
||||
// Extract the VM specific prefix used by each line.
|
||||
try {
|
||||
@@ -51,10 +48,7 @@ export function describeBuiltInComponentFrame(
|
||||
}
|
||||
|
||||
export function describeDebugInfoFrame(name: string, env: ?string): string {
|
||||
return describeBuiltInComponentFrame(
|
||||
name + (env ? ' (' + env + ')' : ''),
|
||||
null,
|
||||
);
|
||||
return describeBuiltInComponentFrame(name + (env ? ' (' + env + ')' : ''));
|
||||
}
|
||||
|
||||
let reentry = false;
|
||||
@@ -292,7 +286,6 @@ export function describeNativeComponentFrame(
|
||||
|
||||
export function describeClassComponentFrame(
|
||||
ctor: Function,
|
||||
ownerFn: void | null | Function,
|
||||
currentDispatcherRef: CurrentDispatcherRef,
|
||||
): string {
|
||||
return describeNativeComponentFrame(ctor, true, currentDispatcherRef);
|
||||
@@ -300,7 +293,6 @@ export function describeClassComponentFrame(
|
||||
|
||||
export function describeFunctionComponentFrame(
|
||||
fn: Function,
|
||||
ownerFn: void | null | Function,
|
||||
currentDispatcherRef: CurrentDispatcherRef,
|
||||
): string {
|
||||
return describeNativeComponentFrame(fn, false, currentDispatcherRef);
|
||||
@@ -313,7 +305,6 @@ function shouldConstruct(Component: Function) {
|
||||
|
||||
export function describeUnknownElementTypeFrameInDEV(
|
||||
type: any,
|
||||
ownerFn: void | null | Function,
|
||||
currentDispatcherRef: CurrentDispatcherRef,
|
||||
): string {
|
||||
if (!__DEV__) {
|
||||
@@ -330,15 +321,15 @@ export function describeUnknownElementTypeFrameInDEV(
|
||||
);
|
||||
}
|
||||
if (typeof type === 'string') {
|
||||
return describeBuiltInComponentFrame(type, ownerFn);
|
||||
return describeBuiltInComponentFrame(type);
|
||||
}
|
||||
switch (type) {
|
||||
case SUSPENSE_NUMBER:
|
||||
case SUSPENSE_SYMBOL_STRING:
|
||||
return describeBuiltInComponentFrame('Suspense', ownerFn);
|
||||
return describeBuiltInComponentFrame('Suspense');
|
||||
case SUSPENSE_LIST_NUMBER:
|
||||
case SUSPENSE_LIST_SYMBOL_STRING:
|
||||
return describeBuiltInComponentFrame('SuspenseList', ownerFn);
|
||||
return describeBuiltInComponentFrame('SuspenseList');
|
||||
}
|
||||
if (typeof type === 'object') {
|
||||
switch (type.$$typeof) {
|
||||
@@ -346,7 +337,6 @@ export function describeUnknownElementTypeFrameInDEV(
|
||||
case FORWARD_REF_SYMBOL_STRING:
|
||||
return describeFunctionComponentFrame(
|
||||
type.render,
|
||||
ownerFn,
|
||||
currentDispatcherRef,
|
||||
);
|
||||
case MEMO_NUMBER:
|
||||
@@ -354,7 +344,6 @@ export function describeUnknownElementTypeFrameInDEV(
|
||||
// Memo may contain any component type so we recursively resolve it.
|
||||
return describeUnknownElementTypeFrameInDEV(
|
||||
type.type,
|
||||
ownerFn,
|
||||
currentDispatcherRef,
|
||||
);
|
||||
case LAZY_NUMBER:
|
||||
@@ -366,7 +355,6 @@ export function describeUnknownElementTypeFrameInDEV(
|
||||
// Lazy may contain any component type so we recursively resolve it.
|
||||
return describeUnknownElementTypeFrameInDEV(
|
||||
init(payload),
|
||||
ownerFn,
|
||||
currentDispatcherRef,
|
||||
);
|
||||
} catch (x) {}
|
||||
|
||||
@@ -39,38 +39,30 @@ export function describeFiber(
|
||||
ClassComponent,
|
||||
} = workTagMap;
|
||||
|
||||
const owner: null | Function = __DEV__
|
||||
? workInProgress._debugOwner
|
||||
? workInProgress._debugOwner.type
|
||||
: null
|
||||
: null;
|
||||
switch (workInProgress.tag) {
|
||||
case HostComponent:
|
||||
return describeBuiltInComponentFrame(workInProgress.type, owner);
|
||||
return describeBuiltInComponentFrame(workInProgress.type);
|
||||
case LazyComponent:
|
||||
return describeBuiltInComponentFrame('Lazy', owner);
|
||||
return describeBuiltInComponentFrame('Lazy');
|
||||
case SuspenseComponent:
|
||||
return describeBuiltInComponentFrame('Suspense', owner);
|
||||
return describeBuiltInComponentFrame('Suspense');
|
||||
case SuspenseListComponent:
|
||||
return describeBuiltInComponentFrame('SuspenseList', owner);
|
||||
return describeBuiltInComponentFrame('SuspenseList');
|
||||
case FunctionComponent:
|
||||
case IndeterminateComponent:
|
||||
case SimpleMemoComponent:
|
||||
return describeFunctionComponentFrame(
|
||||
workInProgress.type,
|
||||
owner,
|
||||
currentDispatcherRef,
|
||||
);
|
||||
case ForwardRef:
|
||||
return describeFunctionComponentFrame(
|
||||
workInProgress.type.render,
|
||||
owner,
|
||||
currentDispatcherRef,
|
||||
);
|
||||
case ClassComponent:
|
||||
return describeClassComponentFrame(
|
||||
workInProgress.type,
|
||||
owner,
|
||||
currentDispatcherRef,
|
||||
);
|
||||
default:
|
||||
|
||||
+35
-18
@@ -1952,15 +1952,24 @@ export function attach(
|
||||
const {key} = fiber;
|
||||
const displayName = getDisplayNameForFiber(fiber);
|
||||
const elementType = getElementTypeForFiber(fiber);
|
||||
const {_debugOwner} = fiber;
|
||||
const debugOwner = fiber._debugOwner;
|
||||
|
||||
// Ideally we should call getFiberIDThrows() for _debugOwner,
|
||||
// since owners are almost always higher in the tree (and so have already been processed),
|
||||
// but in some (rare) instances reported in open source, a descendant mounts before an owner.
|
||||
// Since this is a DEV only field it's probably okay to also just lazily generate and ID here if needed.
|
||||
// See https://github.com/facebook/react/issues/21445
|
||||
const ownerID =
|
||||
_debugOwner != null ? getOrGenerateFiberID(_debugOwner) : 0;
|
||||
let ownerID: number;
|
||||
if (debugOwner != null) {
|
||||
if (typeof debugOwner.tag === 'number') {
|
||||
ownerID = getOrGenerateFiberID((debugOwner: any));
|
||||
} else {
|
||||
// TODO: Track Server Component Owners.
|
||||
ownerID = 0;
|
||||
}
|
||||
} else {
|
||||
ownerID = 0;
|
||||
}
|
||||
const parentID = parentFiber ? getFiberIDThrows(parentFiber) : 0;
|
||||
|
||||
const displayNameStringID = getStringID(displayName);
|
||||
@@ -3104,15 +3113,17 @@ export function attach(
|
||||
return null;
|
||||
}
|
||||
|
||||
const {_debugOwner} = fiber;
|
||||
|
||||
const owners: Array<SerializedElement> = [fiberToSerializedElement(fiber)];
|
||||
|
||||
if (_debugOwner) {
|
||||
let owner: null | Fiber = _debugOwner;
|
||||
while (owner !== null) {
|
||||
owners.unshift(fiberToSerializedElement(owner));
|
||||
owner = owner._debugOwner || null;
|
||||
let owner = fiber._debugOwner;
|
||||
while (owner != null) {
|
||||
if (typeof owner.tag === 'number') {
|
||||
const ownerFiber: Fiber = (owner: any); // Refined
|
||||
owners.unshift(fiberToSerializedElement(ownerFiber));
|
||||
owner = ownerFiber._debugOwner;
|
||||
} else {
|
||||
// TODO: Track Server Component Owners.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3173,7 +3184,7 @@ export function attach(
|
||||
}
|
||||
|
||||
const {
|
||||
_debugOwner,
|
||||
_debugOwner: debugOwner,
|
||||
stateNode,
|
||||
key,
|
||||
memoizedProps,
|
||||
@@ -3300,13 +3311,19 @@ export function attach(
|
||||
context = {value: context};
|
||||
}
|
||||
|
||||
let owners = null;
|
||||
if (_debugOwner) {
|
||||
owners = ([]: Array<SerializedElement>);
|
||||
let owner: null | Fiber = _debugOwner;
|
||||
while (owner !== null) {
|
||||
owners.push(fiberToSerializedElement(owner));
|
||||
owner = owner._debugOwner || null;
|
||||
let owners: null | Array<SerializedElement> = null;
|
||||
let owner = debugOwner;
|
||||
while (owner != null) {
|
||||
if (typeof owner.tag === 'number') {
|
||||
const ownerFiber: Fiber = (owner: any); // Refined
|
||||
if (owners === null) {
|
||||
owners = [];
|
||||
}
|
||||
owners.push(fiberToSerializedElement(ownerFiber));
|
||||
owner = ownerFiber._debugOwner;
|
||||
} else {
|
||||
// TODO: Track Server Component Owners.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -103,13 +103,21 @@ function getInspectorDataForInstance(
|
||||
}
|
||||
|
||||
const fiber = findCurrentFiberUsingSlowPath(closestInstance);
|
||||
if (fiber === null) {
|
||||
// Might not be currently mounted.
|
||||
return {
|
||||
hierarchy: [],
|
||||
props: emptyObject,
|
||||
selectedIndex: null,
|
||||
componentStack: '',
|
||||
};
|
||||
}
|
||||
const fiberHierarchy = getOwnerHierarchy(fiber);
|
||||
const instance = lastNonHostInstance(fiberHierarchy);
|
||||
const hierarchy = createHierarchy(fiberHierarchy);
|
||||
const props = getHostProps(instance);
|
||||
const selectedIndex = fiberHierarchy.indexOf(instance);
|
||||
const componentStack =
|
||||
fiber !== null ? getStackByFiberInDevAndProd(fiber) : '';
|
||||
const componentStack = getStackByFiberInDevAndProd(fiber);
|
||||
|
||||
return {
|
||||
closestInstance: instance,
|
||||
@@ -125,7 +133,7 @@ function getInspectorDataForInstance(
|
||||
);
|
||||
}
|
||||
|
||||
function getOwnerHierarchy(instance: any) {
|
||||
function getOwnerHierarchy(instance: Fiber) {
|
||||
const hierarchy: Array<$FlowFixMe> = [];
|
||||
traverseOwnerTreeUp(hierarchy, instance);
|
||||
return hierarchy;
|
||||
@@ -143,15 +151,17 @@ function lastNonHostInstance(hierarchy) {
|
||||
return hierarchy[0];
|
||||
}
|
||||
|
||||
// $FlowFixMe[missing-local-annot]
|
||||
function traverseOwnerTreeUp(
|
||||
hierarchy: Array<$FlowFixMe>,
|
||||
instance: any,
|
||||
instance: Fiber,
|
||||
): void {
|
||||
if (__DEV__ || enableGetInspectorDataForInstanceInProduction) {
|
||||
if (instance) {
|
||||
hierarchy.unshift(instance);
|
||||
traverseOwnerTreeUp(hierarchy, instance._debugOwner);
|
||||
hierarchy.unshift(instance);
|
||||
const owner = instance._debugOwner;
|
||||
if (owner != null && typeof owner.tag === 'number') {
|
||||
traverseOwnerTreeUp(hierarchy, (owner: any));
|
||||
} else {
|
||||
// TODO: Traverse Server Components owners.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -11,7 +11,7 @@ import type {Fiber} from './ReactInternalTypes';
|
||||
|
||||
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||
import {getStackByFiberInDevAndProd} from './ReactFiberComponentStack';
|
||||
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
|
||||
import {getComponentNameFromOwner} from 'react-reconciler/src/getComponentNameFromFiber';
|
||||
|
||||
const ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
|
||||
|
||||
@@ -24,8 +24,8 @@ export function getCurrentFiberOwnerNameInDevOrNull(): string | null {
|
||||
return null;
|
||||
}
|
||||
const owner = current._debugOwner;
|
||||
if (owner !== null && typeof owner !== 'undefined') {
|
||||
return getComponentNameFromFiber(owner);
|
||||
if (owner != null) {
|
||||
return getComponentNameFromOwner(owner);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
+4
-3
@@ -68,7 +68,7 @@ import {
|
||||
TracingMarkerComponent,
|
||||
} from './ReactWorkTags';
|
||||
import {OffscreenVisible} from './ReactFiberActivityComponent';
|
||||
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
|
||||
import {getComponentNameFromOwner} from 'react-reconciler/src/getComponentNameFromFiber';
|
||||
import {isDevToolsPresent} from './ReactFiberDevToolsHook';
|
||||
import {
|
||||
resolveClassForHotReloading,
|
||||
@@ -110,6 +110,7 @@ import {
|
||||
attachOffscreenInstance,
|
||||
} from './ReactFiberCommitWork';
|
||||
import {getHostContext} from './ReactFiberHostContext';
|
||||
import type {ReactComponentInfo} from '../../shared/ReactTypes';
|
||||
|
||||
export type {Fiber};
|
||||
|
||||
@@ -475,7 +476,7 @@ export function createFiberFromTypeAndProps(
|
||||
type: any, // React$ElementType
|
||||
key: null | string,
|
||||
pendingProps: any,
|
||||
owner: null | Fiber,
|
||||
owner: null | ReactComponentInfo | Fiber,
|
||||
mode: TypeOfMode,
|
||||
lanes: Lanes,
|
||||
): Fiber {
|
||||
@@ -610,7 +611,7 @@ export function createFiberFromTypeAndProps(
|
||||
"it's defined in, or you might have mixed up default and " +
|
||||
'named imports.';
|
||||
}
|
||||
const ownerName = owner ? getComponentNameFromFiber(owner) : null;
|
||||
const ownerName = owner ? getComponentNameFromOwner(owner) : null;
|
||||
if (ownerName) {
|
||||
info += '\n\nCheck the render method of `' + ownerName + '`.';
|
||||
}
|
||||
|
||||
+7
-12
@@ -29,29 +29,24 @@ import {
|
||||
} from 'shared/ReactComponentStackFrame';
|
||||
|
||||
function describeFiber(fiber: Fiber): string {
|
||||
const owner: null | Function = __DEV__
|
||||
? fiber._debugOwner
|
||||
? fiber._debugOwner.type
|
||||
: null
|
||||
: null;
|
||||
switch (fiber.tag) {
|
||||
case HostHoistable:
|
||||
case HostSingleton:
|
||||
case HostComponent:
|
||||
return describeBuiltInComponentFrame(fiber.type, owner);
|
||||
return describeBuiltInComponentFrame(fiber.type);
|
||||
case LazyComponent:
|
||||
return describeBuiltInComponentFrame('Lazy', owner);
|
||||
return describeBuiltInComponentFrame('Lazy');
|
||||
case SuspenseComponent:
|
||||
return describeBuiltInComponentFrame('Suspense', owner);
|
||||
return describeBuiltInComponentFrame('Suspense');
|
||||
case SuspenseListComponent:
|
||||
return describeBuiltInComponentFrame('SuspenseList', owner);
|
||||
return describeBuiltInComponentFrame('SuspenseList');
|
||||
case FunctionComponent:
|
||||
case SimpleMemoComponent:
|
||||
return describeFunctionComponentFrame(fiber.type, owner);
|
||||
return describeFunctionComponentFrame(fiber.type);
|
||||
case ForwardRef:
|
||||
return describeFunctionComponentFrame(fiber.type.render, owner);
|
||||
return describeFunctionComponentFrame(fiber.type.render);
|
||||
case ClassComponent:
|
||||
return describeClassComponentFrame(fiber.type, owner);
|
||||
return describeClassComponentFrame(fiber.type);
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
|
||||
+2
-1
@@ -15,6 +15,7 @@ import type {
|
||||
Usable,
|
||||
ReactFormState,
|
||||
Awaited,
|
||||
ReactComponentInfo,
|
||||
ReactDebugInfo,
|
||||
} from 'shared/ReactTypes';
|
||||
import type {WorkTag} from './ReactWorkTags';
|
||||
@@ -193,7 +194,7 @@ export type Fiber = {
|
||||
// __DEV__ only
|
||||
|
||||
_debugInfo?: ReactDebugInfo | null,
|
||||
_debugOwner?: Fiber | null,
|
||||
_debugOwner?: ReactComponentInfo | Fiber | null,
|
||||
_debugIsCurrentlyTiming?: boolean,
|
||||
_debugNeedsRemount?: boolean,
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ import {
|
||||
} from 'react-reconciler/src/ReactWorkTags';
|
||||
import getComponentNameFromType from 'shared/getComponentNameFromType';
|
||||
import {REACT_STRICT_MODE_TYPE} from 'shared/ReactSymbols';
|
||||
import type {ReactComponentInfo} from '../../shared/ReactTypes';
|
||||
|
||||
// Keep in sync with shared/getComponentNameFromType
|
||||
function getWrappedName(
|
||||
@@ -66,6 +67,18 @@ function getContextName(type: ReactContext<any>) {
|
||||
return type.displayName || 'Context';
|
||||
}
|
||||
|
||||
export function getComponentNameFromOwner(
|
||||
owner: Fiber | ReactComponentInfo,
|
||||
): string | null {
|
||||
if (typeof owner.tag === 'number') {
|
||||
return getComponentNameFromFiber((owner: any));
|
||||
}
|
||||
if (typeof owner.name === 'string') {
|
||||
return owner.name;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export default function getComponentNameFromFiber(fiber: Fiber): string | null {
|
||||
const {tag, type} = fiber;
|
||||
switch (tag) {
|
||||
|
||||
@@ -290,7 +290,7 @@ describe('ReactFlightDOMEdge', () => {
|
||||
<ServerComponent recurse={20} />,
|
||||
);
|
||||
const serializedContent = await readResult(stream);
|
||||
const expectedDebugInfoSize = __DEV__ ? 42 * 20 : 0;
|
||||
const expectedDebugInfoSize = __DEV__ ? 64 * 20 : 0;
|
||||
expect(serializedContent.length).toBeLessThan(150 + expectedDebugInfoSize);
|
||||
});
|
||||
|
||||
|
||||
+3
-3
@@ -43,13 +43,13 @@ export function getStackByComponentStackNode(
|
||||
do {
|
||||
switch (node.tag) {
|
||||
case 0:
|
||||
info += describeBuiltInComponentFrame(node.type, null);
|
||||
info += describeBuiltInComponentFrame(node.type);
|
||||
break;
|
||||
case 1:
|
||||
info += describeFunctionComponentFrame(node.type, null);
|
||||
info += describeFunctionComponentFrame(node.type);
|
||||
break;
|
||||
case 2:
|
||||
info += describeClassComponentFrame(node.type, null);
|
||||
info += describeClassComponentFrame(node.type);
|
||||
break;
|
||||
}
|
||||
// $FlowFixMe[incompatible-type] we bail out when we get a null
|
||||
|
||||
+12
-1
@@ -9,7 +9,7 @@
|
||||
|
||||
import type {Dispatcher} from 'react-reconciler/src/ReactInternalTypes';
|
||||
import type {Request} from './ReactFlightServer';
|
||||
import type {Thenable, Usable} from 'shared/ReactTypes';
|
||||
import type {Thenable, Usable, ReactComponentInfo} from 'shared/ReactTypes';
|
||||
import type {ThenableState} from './ReactFlightThenable';
|
||||
import {
|
||||
REACT_MEMO_CACHE_SENTINEL,
|
||||
@@ -21,6 +21,7 @@ import {isClientReference} from './ReactFlightServerConfig';
|
||||
let currentRequest = null;
|
||||
let thenableIndexCounter = 0;
|
||||
let thenableState = null;
|
||||
let currentComponentDebugInfo = null;
|
||||
|
||||
export function prepareToUseHooksForRequest(request: Request) {
|
||||
currentRequest = request;
|
||||
@@ -32,9 +33,13 @@ export function resetHooksForRequest() {
|
||||
|
||||
export function prepareToUseHooksForComponent(
|
||||
prevThenableState: ThenableState | null,
|
||||
componentDebugInfo: null | ReactComponentInfo,
|
||||
) {
|
||||
thenableIndexCounter = 0;
|
||||
thenableState = prevThenableState;
|
||||
if (__DEV__) {
|
||||
currentComponentDebugInfo = componentDebugInfo;
|
||||
}
|
||||
}
|
||||
|
||||
export function getThenableStateAfterSuspending(): ThenableState {
|
||||
@@ -42,6 +47,12 @@ export function getThenableStateAfterSuspending(): ThenableState {
|
||||
// which is not really supported anymore, it will be empty. We use the empty set as a
|
||||
// marker to know if this was a replay of the same component or first attempt.
|
||||
const state = thenableState || createThenableState();
|
||||
if (__DEV__) {
|
||||
// This is a hack but we stash the debug info here so that we don't need a completely
|
||||
// different data structure just for this in DEV. Not too happy about it.
|
||||
(state: any)._componentDebugInfo = currentComponentDebugInfo;
|
||||
currentComponentDebugInfo = null;
|
||||
}
|
||||
thenableState = null;
|
||||
return state;
|
||||
}
|
||||
|
||||
+85
-18
@@ -58,6 +58,7 @@ import type {
|
||||
ReactComponentInfo,
|
||||
ReactAsyncInfo,
|
||||
} from 'shared/ReactTypes';
|
||||
import type {ReactElement} from 'shared/ReactElementType';
|
||||
import type {LazyComponent} from 'react/src/ReactLazy';
|
||||
import type {TemporaryReference} from './ReactFlightServerTemporaryReferences';
|
||||
|
||||
@@ -153,7 +154,8 @@ function patchConsole(consoleInst: typeof console, methodName: string) {
|
||||
// We don't currently use this id for anything but we emit it so that we can later
|
||||
// refer to previous logs in debug info to associate them with a component.
|
||||
const id = request.nextChunkId++;
|
||||
emitConsoleChunk(request, id, methodName, stack, arguments);
|
||||
const owner: null | ReactComponentInfo = ReactCurrentOwner.current;
|
||||
emitConsoleChunk(request, id, methodName, owner, stack, arguments);
|
||||
}
|
||||
// $FlowFixMe[prop-missing]
|
||||
return originalMethod.apply(this, arguments);
|
||||
@@ -303,6 +305,7 @@ const {
|
||||
ReactCurrentCache,
|
||||
} = ReactServerSharedInternals;
|
||||
const ReactCurrentDispatcher = ReactSharedInternals.ReactCurrentDispatcher;
|
||||
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
|
||||
|
||||
function throwTaintViolation(message: string) {
|
||||
// eslint-disable-next-line react-internal/prod-error-codes
|
||||
@@ -594,6 +597,7 @@ function renderFunctionComponent<Props>(
|
||||
key: null | string,
|
||||
Component: (p: Props, arg: void) => any,
|
||||
props: Props,
|
||||
owner: null | ReactComponentInfo,
|
||||
): ReactJSONValue {
|
||||
// Reset the task's thenable state before continuing, so that if a later
|
||||
// component suspends we can reuse the same task object. If the same
|
||||
@@ -601,6 +605,7 @@ function renderFunctionComponent<Props>(
|
||||
const prevThenableState = task.thenableState;
|
||||
task.thenableState = null;
|
||||
|
||||
let componentDebugInfo: null | ReactComponentInfo = null;
|
||||
if (__DEV__) {
|
||||
if (debugID === null) {
|
||||
// We don't have a chunk to assign debug info. We need to outline this
|
||||
@@ -609,22 +614,42 @@ function renderFunctionComponent<Props>(
|
||||
} else if (prevThenableState !== null) {
|
||||
// This is a replay and we've already emitted the debug info of this component
|
||||
// in the first pass. We skip emitting a duplicate line.
|
||||
// As a hack we stashed the previous component debug info on this object in DEV.
|
||||
componentDebugInfo = (prevThenableState: any)._componentDebugInfo;
|
||||
} else {
|
||||
// This is a new component in the same task so we can emit more debug info.
|
||||
const componentName =
|
||||
(Component: any).displayName || Component.name || '';
|
||||
request.pendingChunks++;
|
||||
emitDebugChunk(request, debugID, {
|
||||
|
||||
const componentDebugID = debugID;
|
||||
componentDebugInfo = {
|
||||
name: componentName,
|
||||
env: request.environmentName,
|
||||
});
|
||||
owner: owner,
|
||||
};
|
||||
// We outline this model eagerly so that we can refer to by reference as an owner.
|
||||
// If we had a smarter way to dedupe we might not have to do this if there ends up
|
||||
// being no references to this as an owner.
|
||||
outlineModel(request, componentDebugInfo);
|
||||
emitDebugChunk(request, componentDebugID, componentDebugInfo);
|
||||
}
|
||||
}
|
||||
|
||||
prepareToUseHooksForComponent(prevThenableState);
|
||||
prepareToUseHooksForComponent(prevThenableState, componentDebugInfo);
|
||||
// The secondArg is always undefined in Server Components since refs error early.
|
||||
const secondArg = undefined;
|
||||
let result = Component(props, secondArg);
|
||||
let result;
|
||||
if (__DEV__) {
|
||||
ReactCurrentOwner.current = componentDebugInfo;
|
||||
try {
|
||||
result = Component(props, secondArg);
|
||||
} finally {
|
||||
ReactCurrentOwner.current = null;
|
||||
}
|
||||
} else {
|
||||
result = Component(props, secondArg);
|
||||
}
|
||||
if (
|
||||
typeof result === 'object' &&
|
||||
result !== null &&
|
||||
@@ -723,9 +748,12 @@ function renderClientElement(
|
||||
type: any,
|
||||
key: null | string,
|
||||
props: any,
|
||||
owner: null | ReactComponentInfo, // DEV-only
|
||||
): ReactJSONValue {
|
||||
if (!enableServerComponentKeys) {
|
||||
return [REACT_ELEMENT_TYPE, type, key, props];
|
||||
return __DEV__
|
||||
? [REACT_ELEMENT_TYPE, type, key, props, owner]
|
||||
: [REACT_ELEMENT_TYPE, type, key, props];
|
||||
}
|
||||
// We prepend the terminal client element that actually gets serialized with
|
||||
// the keys of any Server Components which are not serialized.
|
||||
@@ -735,7 +763,9 @@ function renderClientElement(
|
||||
} else if (keyPath !== null) {
|
||||
key = keyPath + ',' + key;
|
||||
}
|
||||
const element = [REACT_ELEMENT_TYPE, type, key, props];
|
||||
const element = __DEV__
|
||||
? [REACT_ELEMENT_TYPE, type, key, props, owner]
|
||||
: [REACT_ELEMENT_TYPE, type, key, props];
|
||||
if (task.implicitSlot && key !== null) {
|
||||
// The root Server Component had no key so it was in an implicit slot.
|
||||
// If we had a key lower, it would end up in that slot with an explicit key.
|
||||
@@ -781,6 +811,7 @@ function renderElement(
|
||||
key: null | string,
|
||||
ref: mixed,
|
||||
props: any,
|
||||
owner: null | ReactComponentInfo, // DEV only
|
||||
): ReactJSONValue {
|
||||
if (ref !== null && ref !== undefined) {
|
||||
// When the ref moves to the regular props object this will implicitly
|
||||
@@ -801,13 +832,13 @@ function renderElement(
|
||||
if (typeof type === 'function') {
|
||||
if (isClientReference(type) || isTemporaryReference(type)) {
|
||||
// This is a reference to a Client Component.
|
||||
return renderClientElement(task, type, key, props);
|
||||
return renderClientElement(task, type, key, props, owner);
|
||||
}
|
||||
// This is a Server Component.
|
||||
return renderFunctionComponent(request, task, key, type, props);
|
||||
return renderFunctionComponent(request, task, key, type, props, owner);
|
||||
} else if (typeof type === 'string') {
|
||||
// This is a host element. E.g. HTML.
|
||||
return renderClientElement(task, type, key, props);
|
||||
return renderClientElement(task, type, key, props, owner);
|
||||
} else if (typeof type === 'symbol') {
|
||||
if (type === REACT_FRAGMENT_TYPE && key === null) {
|
||||
// For key-less fragments, we add a small optimization to avoid serializing
|
||||
@@ -828,24 +859,39 @@ function renderElement(
|
||||
}
|
||||
// This might be a built-in React component. We'll let the client decide.
|
||||
// Any built-in works as long as its props are serializable.
|
||||
return renderClientElement(task, type, key, props);
|
||||
return renderClientElement(task, type, key, props, owner);
|
||||
} else if (type != null && typeof type === 'object') {
|
||||
if (isClientReference(type)) {
|
||||
// This is a reference to a Client Component.
|
||||
return renderClientElement(task, type, key, props);
|
||||
return renderClientElement(task, type, key, props, owner);
|
||||
}
|
||||
switch (type.$$typeof) {
|
||||
case REACT_LAZY_TYPE: {
|
||||
const payload = type._payload;
|
||||
const init = type._init;
|
||||
const wrappedType = init(payload);
|
||||
return renderElement(request, task, wrappedType, key, ref, props);
|
||||
return renderElement(
|
||||
request,
|
||||
task,
|
||||
wrappedType,
|
||||
key,
|
||||
ref,
|
||||
props,
|
||||
owner,
|
||||
);
|
||||
}
|
||||
case REACT_FORWARD_REF_TYPE: {
|
||||
return renderFunctionComponent(request, task, key, type.render, props);
|
||||
return renderFunctionComponent(
|
||||
request,
|
||||
task,
|
||||
key,
|
||||
type.render,
|
||||
props,
|
||||
owner,
|
||||
);
|
||||
}
|
||||
case REACT_MEMO_TYPE: {
|
||||
return renderElement(request, task, type.type, key, ref, props);
|
||||
return renderElement(request, task, type.type, key, ref, props, owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1356,7 +1402,7 @@ function renderModelDestructive(
|
||||
writtenObjects.set((value: any).props, NEVER_OUTLINED);
|
||||
}
|
||||
|
||||
const element: React$Element<any> = (value: any);
|
||||
const element: ReactElement = (value: any);
|
||||
|
||||
if (__DEV__) {
|
||||
const debugInfo: ?ReactDebugInfo = (value: any)._debugInfo;
|
||||
@@ -1394,6 +1440,7 @@ function renderModelDestructive(
|
||||
element.key,
|
||||
ref,
|
||||
props,
|
||||
__DEV__ ? element._owner : null,
|
||||
);
|
||||
}
|
||||
case REACT_LAZY_TYPE: {
|
||||
@@ -1904,8 +1951,27 @@ function emitDebugChunk(
|
||||
);
|
||||
}
|
||||
|
||||
// We use the console encoding so that we can dedupe objects but don't necessarily
|
||||
// use the full serialization that requires a task.
|
||||
const counter = {objectCount: 0};
|
||||
function replacer(
|
||||
this:
|
||||
| {+[key: string | number]: ReactClientValue}
|
||||
| $ReadOnlyArray<ReactClientValue>,
|
||||
parentPropertyName: string,
|
||||
value: ReactClientValue,
|
||||
): ReactJSONValue {
|
||||
return renderConsoleValue(
|
||||
request,
|
||||
counter,
|
||||
this,
|
||||
parentPropertyName,
|
||||
value,
|
||||
);
|
||||
}
|
||||
|
||||
// $FlowFixMe[incompatible-type] stringify can return null
|
||||
const json: string = stringify(debugInfo);
|
||||
const json: string = stringify(debugInfo, replacer);
|
||||
const row = serializeRowHeader('D', id) + json + '\n';
|
||||
const processedChunk = stringToChunk(row);
|
||||
request.completedRegularChunks.push(processedChunk);
|
||||
@@ -2207,6 +2273,7 @@ function emitConsoleChunk(
|
||||
request: Request,
|
||||
id: number,
|
||||
methodName: string,
|
||||
owner: null | ReactComponentInfo,
|
||||
stackTrace: string,
|
||||
args: Array<any>,
|
||||
): void {
|
||||
@@ -2241,7 +2308,7 @@ function emitConsoleChunk(
|
||||
|
||||
// TODO: Don't double badge if this log came from another Flight Client.
|
||||
const env = request.environmentName;
|
||||
const payload = [methodName, stackTrace, env];
|
||||
const payload = [methodName, stackTrace, owner, env];
|
||||
// $FlowFixMe[method-unbinding]
|
||||
payload.push.apply(payload, args);
|
||||
// $FlowFixMe[incompatible-type] stringify can return null
|
||||
|
||||
@@ -86,7 +86,7 @@ describe('ReactFetch', () => {
|
||||
const promise = render(Component);
|
||||
expect(await promise).toMatchInlineSnapshot(`"GET world []"`);
|
||||
expect(promise._debugInfo).toEqual(
|
||||
__DEV__ ? [{name: 'Component', env: 'Server'}] : undefined,
|
||||
__DEV__ ? [{name: 'Component', env: 'Server', owner: null}] : undefined,
|
||||
);
|
||||
expect(fetchCount).toBe(1);
|
||||
});
|
||||
|
||||
@@ -1051,13 +1051,17 @@ function validateExplicitKey(element, parentType) {
|
||||
let childOwner = '';
|
||||
if (
|
||||
element &&
|
||||
element._owner &&
|
||||
element._owner != null &&
|
||||
element._owner !== ReactCurrentOwner.current
|
||||
) {
|
||||
let ownerName = null;
|
||||
if (typeof element._owner.tag === 'number') {
|
||||
ownerName = getComponentNameFromType(element._owner.type);
|
||||
} else if (typeof element._owner.name === 'string') {
|
||||
ownerName = element._owner.name;
|
||||
}
|
||||
// Give the component that originally created this child.
|
||||
childOwner = ` It was passed a child from ${getComponentNameFromType(
|
||||
element._owner.type,
|
||||
)}.`;
|
||||
childOwner = ` It was passed a child from ${ownerName}.`;
|
||||
}
|
||||
|
||||
setCurrentlyValidatingElement(element);
|
||||
|
||||
@@ -26,10 +26,7 @@ import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||
const {ReactCurrentDispatcher} = ReactSharedInternals;
|
||||
|
||||
let prefix;
|
||||
export function describeBuiltInComponentFrame(
|
||||
name: string,
|
||||
ownerFn: void | null | Function,
|
||||
): string {
|
||||
export function describeBuiltInComponentFrame(name: string): string {
|
||||
if (enableComponentStackLocations) {
|
||||
if (prefix === undefined) {
|
||||
// Extract the VM specific prefix used by each line.
|
||||
@@ -43,19 +40,12 @@ export function describeBuiltInComponentFrame(
|
||||
// We use the prefix to ensure our stacks line up with native stack frames.
|
||||
return '\n' + prefix + name;
|
||||
} else {
|
||||
let ownerName = null;
|
||||
if (__DEV__ && ownerFn) {
|
||||
ownerName = ownerFn.displayName || ownerFn.name || null;
|
||||
}
|
||||
return describeComponentFrame(name, ownerName);
|
||||
return describeComponentFrame(name);
|
||||
}
|
||||
}
|
||||
|
||||
export function describeDebugInfoFrame(name: string, env: ?string): string {
|
||||
return describeBuiltInComponentFrame(
|
||||
name + (env ? ' (' + env + ')' : ''),
|
||||
null,
|
||||
);
|
||||
return describeBuiltInComponentFrame(name + (env ? ' (' + env + ')' : ''));
|
||||
}
|
||||
|
||||
let reentry = false;
|
||||
@@ -298,29 +288,19 @@ export function describeNativeComponentFrame(
|
||||
return syntheticFrame;
|
||||
}
|
||||
|
||||
function describeComponentFrame(name: null | string, ownerName: null | string) {
|
||||
let sourceInfo = '';
|
||||
if (ownerName) {
|
||||
sourceInfo = ' (created by ' + ownerName + ')';
|
||||
}
|
||||
return '\n in ' + (name || 'Unknown') + sourceInfo;
|
||||
function describeComponentFrame(name: null | string) {
|
||||
return '\n in ' + (name || 'Unknown');
|
||||
}
|
||||
|
||||
export function describeClassComponentFrame(
|
||||
ctor: Function,
|
||||
ownerFn: void | null | Function,
|
||||
): string {
|
||||
export function describeClassComponentFrame(ctor: Function): string {
|
||||
if (enableComponentStackLocations) {
|
||||
return describeNativeComponentFrame(ctor, true);
|
||||
} else {
|
||||
return describeFunctionComponentFrame(ctor, ownerFn);
|
||||
return describeFunctionComponentFrame(ctor);
|
||||
}
|
||||
}
|
||||
|
||||
export function describeFunctionComponentFrame(
|
||||
fn: Function,
|
||||
ownerFn: void | null | Function,
|
||||
): string {
|
||||
export function describeFunctionComponentFrame(fn: Function): string {
|
||||
if (enableComponentStackLocations) {
|
||||
return describeNativeComponentFrame(fn, false);
|
||||
} else {
|
||||
@@ -328,11 +308,7 @@ export function describeFunctionComponentFrame(
|
||||
return '';
|
||||
}
|
||||
const name = fn.displayName || fn.name || null;
|
||||
let ownerName = null;
|
||||
if (__DEV__ && ownerFn) {
|
||||
ownerName = ownerFn.displayName || ownerFn.name || null;
|
||||
}
|
||||
return describeComponentFrame(name, ownerName);
|
||||
return describeComponentFrame(name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,10 +317,7 @@ function shouldConstruct(Component: Function) {
|
||||
return !!(prototype && prototype.isReactComponent);
|
||||
}
|
||||
|
||||
export function describeUnknownElementTypeFrameInDEV(
|
||||
type: any,
|
||||
ownerFn: void | null | Function,
|
||||
): string {
|
||||
export function describeUnknownElementTypeFrameInDEV(type: any): string {
|
||||
if (!__DEV__) {
|
||||
return '';
|
||||
}
|
||||
@@ -355,32 +328,32 @@ export function describeUnknownElementTypeFrameInDEV(
|
||||
if (enableComponentStackLocations) {
|
||||
return describeNativeComponentFrame(type, shouldConstruct(type));
|
||||
} else {
|
||||
return describeFunctionComponentFrame(type, ownerFn);
|
||||
return describeFunctionComponentFrame(type);
|
||||
}
|
||||
}
|
||||
if (typeof type === 'string') {
|
||||
return describeBuiltInComponentFrame(type, ownerFn);
|
||||
return describeBuiltInComponentFrame(type);
|
||||
}
|
||||
switch (type) {
|
||||
case REACT_SUSPENSE_TYPE:
|
||||
return describeBuiltInComponentFrame('Suspense', ownerFn);
|
||||
return describeBuiltInComponentFrame('Suspense');
|
||||
case REACT_SUSPENSE_LIST_TYPE:
|
||||
return describeBuiltInComponentFrame('SuspenseList', ownerFn);
|
||||
return describeBuiltInComponentFrame('SuspenseList');
|
||||
}
|
||||
if (typeof type === 'object') {
|
||||
switch (type.$$typeof) {
|
||||
case REACT_FORWARD_REF_TYPE:
|
||||
return describeFunctionComponentFrame(type.render, ownerFn);
|
||||
return describeFunctionComponentFrame(type.render);
|
||||
case REACT_MEMO_TYPE:
|
||||
// Memo may contain any component type so we recursively resolve it.
|
||||
return describeUnknownElementTypeFrameInDEV(type.type, ownerFn);
|
||||
return describeUnknownElementTypeFrameInDEV(type.type);
|
||||
case REACT_LAZY_TYPE: {
|
||||
const lazyComponent: LazyComponent<any, any> = (type: any);
|
||||
const payload = lazyComponent._payload;
|
||||
const init = lazyComponent._init;
|
||||
try {
|
||||
// Lazy may contain any component type so we recursively resolve it.
|
||||
return describeUnknownElementTypeFrameInDEV(init(payload), ownerFn);
|
||||
return describeUnknownElementTypeFrameInDEV(init(payload));
|
||||
} catch (x) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,6 +181,7 @@ export type Awaited<T> = T extends null | void
|
||||
export type ReactComponentInfo = {
|
||||
+name?: string,
|
||||
+env?: string,
|
||||
+owner?: null | ReactComponentInfo,
|
||||
};
|
||||
|
||||
export type ReactAsyncInfo = {
|
||||
|
||||
Reference in New Issue
Block a user