Fix User Timing oddities with Suspense, pure, and lazy (#13833)

* Show pure components in fiber timings with name

* Fix Suspense and lazy user timings

* Tweak message and type name

* Fix Flow
This commit is contained in:
Dan Abramov
2018-10-12 03:15:14 +01:00
committed by GitHub
parent d270db1c38
commit 4a635785f5
5 changed files with 126 additions and 8 deletions
+5 -1
View File
@@ -20,6 +20,7 @@ import {
ContextProvider,
ContextConsumer,
Mode,
SuspenseComponent,
} from 'shared/ReactWorkTags';
type MeasurementPhase =
@@ -315,7 +316,10 @@ export function stopFailedWorkTimer(fiber: Fiber): void {
return;
}
fiber._debugIsCurrentlyTiming = false;
const warning = 'An error was thrown inside this error boundary';
const warning =
fiber.tag === SuspenseComponent
? 'Rendering was suspended'
: 'An error was thrown inside this error boundary';
endFiberMark(fiber, null, warning);
}
}
+5 -1
View File
@@ -56,7 +56,7 @@ import ReactStrictModeWarnings from './ReactStrictModeWarnings';
import warning from 'shared/warning';
import warningWithoutStack from 'shared/warningWithoutStack';
import * as ReactCurrentFiber from './ReactCurrentFiber';
import {cancelWorkTimer} from './ReactDebugFiberPerf';
import {startWorkTimer, cancelWorkTimer} from './ReactDebugFiberPerf';
import {
mountChildFibers,
@@ -720,11 +720,15 @@ function mountIndeterminateComponent(
Component !== null &&
typeof Component.then === 'function'
) {
// We can't start a User Timing measurement with correct label yet.
// Cancel and resume right after we know the tag.
cancelWorkTimer(workInProgress);
Component = readLazyComponentType(Component);
const resolvedTag = (workInProgress.tag = resolveLazyComponentTag(
workInProgress,
Component,
));
startWorkTimer(workInProgress);
const resolvedProps = resolveDefaultProps(Component, props);
let child;
switch (resolvedTag) {
@@ -555,6 +555,58 @@ describe('ReactDebugFiberPerf', () => {
expect(getFlameChart()).toMatchSnapshot();
});
it('supports pure', () => {
const PureFoo = React.pure(function Foo() {
return <div />;
});
ReactNoop.render(
<Parent>
<PureFoo />
</Parent>,
);
ReactNoop.flush();
expect(getFlameChart()).toMatchSnapshot();
});
it('supports Suspense and lazy', async () => {
function Spinner() {
return <span />;
}
let resolve;
const LazyFoo = React.lazy(
() =>
new Promise(r => {
resolve = r;
}),
);
ReactNoop.render(
<Parent>
<React.unstable_Suspense fallback={<Spinner />}>
<LazyFoo />
</React.unstable_Suspense>
</Parent>,
);
ReactNoop.flush();
expect(getFlameChart()).toMatchSnapshot();
resolve(function Foo() {
return <div />;
});
await LazyFoo;
ReactNoop.render(
<Parent>
<React.unstable_Suspense>
<LazyFoo />
</React.unstable_Suspense>
</Parent>,
);
ReactNoop.flush();
expect(getFlameChart()).toMatchSnapshot();
});
it('does not schedule an extra callback if setState is called during a synchronous commit phase', () => {
class Component extends React.Component {
state = {step: 1};
@@ -362,6 +362,40 @@ exports[`ReactDebugFiberPerf skips parents during setState 1`] = `
"
`;
exports[`ReactDebugFiberPerf supports Suspense and lazy 1`] = `
"⚛ (Waiting for async callback... will force flush in 5250 ms)
⚛ (React Tree Reconciliation: Completed Root)
⚛ Parent [mount]
⛔ Suspense [mount] Warning: Rendering was suspended
⚛ Suspense [mount]
⚛ Spinner [mount]
"
`;
exports[`ReactDebugFiberPerf supports Suspense and lazy 2`] = `
"⚛ (Waiting for async callback... will force flush in 5250 ms)
⚛ (React Tree Reconciliation: Completed Root)
⚛ Parent [mount]
⛔ Suspense [mount] Warning: Rendering was suspended
⚛ Suspense [mount]
⚛ Spinner [mount]
⚛ (Waiting for async callback... will force flush in 5250 ms)
⚛ (React Tree Reconciliation: Completed Root)
⚛ Parent [mount]
⚛ Suspense [mount]
⚛ Foo [mount]
⚛ (Committing Changes)
⚛ (Committing Snapshot Effects: 0 Total)
⚛ (Committing Host Effects: 1 Total)
⚛ (Calling Lifecycle Methods: 0 Total)
"
`;
exports[`ReactDebugFiberPerf supports portals 1`] = `
"⚛ (Waiting for async callback... will force flush in 5250 ms)
@@ -376,6 +410,20 @@ exports[`ReactDebugFiberPerf supports portals 1`] = `
"
`;
exports[`ReactDebugFiberPerf supports pure 1`] = `
"⚛ (Waiting for async callback... will force flush in 5250 ms)
⚛ (React Tree Reconciliation: Completed Root)
⚛ Parent [mount]
⚛ Pure(Foo) [mount]
⚛ (Committing Changes)
⚛ (Committing Snapshot Effects: 0 Total)
⚛ (Committing Host Effects: 1 Total)
⚛ (Calling Lifecycle Methods: 0 Total)
"
`;
exports[`ReactDebugFiberPerf warns if an in-progress update is interrupted 1`] = `
"⚛ (Waiting for async callback... will force flush in 5250 ms)
+16 -6
View File
@@ -16,6 +16,7 @@ import {
REACT_FORWARD_REF_TYPE,
REACT_FRAGMENT_TYPE,
REACT_PORTAL_TYPE,
REACT_PURE_TYPE,
REACT_PROFILER_TYPE,
REACT_PROVIDER_TYPE,
REACT_STRICT_MODE_TYPE,
@@ -23,6 +24,18 @@ import {
} from 'shared/ReactSymbols';
import {refineResolvedThenable} from 'shared/ReactLazyComponent';
function getWrappedName(
outerType: mixed,
innerType: any,
wrapperName: string,
): string {
const functionName = innerType.displayName || innerType.name || '';
return (
(outerType: any).displayName ||
(functionName !== '' ? `${wrapperName}(${functionName})` : wrapperName)
);
}
function getComponentName(type: mixed): string | null {
if (type == null) {
// Host root, text node or just invalid type.
@@ -64,12 +77,9 @@ function getComponentName(type: mixed): string | null {
case REACT_PROVIDER_TYPE:
return 'Context.Provider';
case REACT_FORWARD_REF_TYPE:
const renderFn = (type.render: any);
const functionName = renderFn.displayName || renderFn.name || '';
return (
(type: any).displayName ||
(functionName !== '' ? `ForwardRef(${functionName})` : 'ForwardRef')
);
return getWrappedName(type, type.render, 'ForwardRef');
case REACT_PURE_TYPE:
return getWrappedName(type, type.render, 'Pure');
}
if (typeof type.then === 'function') {
const thenable: Thenable<mixed> = (type: any);