@samitouri / QOS-React / commits / 4a635785f5

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

Dan Abramov committed Oct 12, 2018 at 03:15 UTC 4a635785f5cc40bde901ada6090904fdc8cc120d
5 files changed +126 -8
packages/react-reconciler/src/ReactDebugFiberPerf.js
+5 -1
@@ -20,6 +20,7 @@ import {
20 ContextProvider,
21 ContextConsumer,
22 Mode,
23 + SuspenseComponent,
24 } from 'shared/ReactWorkTags';
25
26 type MeasurementPhase =
@@ -315,7 +316,10 @@ export function stopFailedWorkTimer(fiber: Fiber): void {
316 return;
317 }
318 fiber._debugIsCurrentlyTiming = false;
318 - const warning = 'An error was thrown inside this error boundary';
319 + const warning =
320 + fiber.tag === SuspenseComponent
321 + ? 'Rendering was suspended'
322 + : 'An error was thrown inside this error boundary';
323 endFiberMark(fiber, null, warning);
324 }
325 }
packages/react-reconciler/src/ReactFiberBeginWork.js
+5 -1
@@ -56,7 +56,7 @@ import ReactStrictModeWarnings from './ReactStrictModeWarnings';
56 import warning from 'shared/warning';
57 import warningWithoutStack from 'shared/warningWithoutStack';
58 import * as ReactCurrentFiber from './ReactCurrentFiber';
59 -import {cancelWorkTimer} from './ReactDebugFiberPerf';
59 +import {startWorkTimer, cancelWorkTimer} from './ReactDebugFiberPerf';
60
61 import {
62 mountChildFibers,
@@ -720,11 +720,15 @@ function mountIndeterminateComponent(
720 Component !== null &&
721 typeof Component.then === 'function'
722 ) {
723 + // We can't start a User Timing measurement with correct label yet.
724 + // Cancel and resume right after we know the tag.
725 + cancelWorkTimer(workInProgress);
726 Component = readLazyComponentType(Component);
727 const resolvedTag = (workInProgress.tag = resolveLazyComponentTag(
728 workInProgress,
729 Component,
730 ));
731 + startWorkTimer(workInProgress);
732 const resolvedProps = resolveDefaultProps(Component, props);
733 let child;
734 switch (resolvedTag) {
packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js
+52
@@ -555,6 +555,58 @@ describe('ReactDebugFiberPerf', () => {
555 expect(getFlameChart()).toMatchSnapshot();
556 });
557
558 + it('supports pure', () => {
559 + const PureFoo = React.pure(function Foo() {
560 + return <div />;
561 + });
562 + ReactNoop.render(
563 + <Parent>
564 + <PureFoo />
565 + </Parent>,
566 + );
567 + ReactNoop.flush();
568 + expect(getFlameChart()).toMatchSnapshot();
569 + });
570 +
571 + it('supports Suspense and lazy', async () => {
572 + function Spinner() {
573 + return <span />;
574 + }
575 +
576 + let resolve;
577 + const LazyFoo = React.lazy(
578 + () =>
579 + new Promise(r => {
580 + resolve = r;
581 + }),
582 + );
583 +
584 + ReactNoop.render(
585 + <Parent>
586 + <React.unstable_Suspense fallback={<Spinner />}>
587 + <LazyFoo />
588 + </React.unstable_Suspense>
589 + </Parent>,
590 + );
591 + ReactNoop.flush();
592 + expect(getFlameChart()).toMatchSnapshot();
593 +
594 + resolve(function Foo() {
595 + return <div />;
596 + });
597 + await LazyFoo;
598 +
599 + ReactNoop.render(
600 + <Parent>
601 + <React.unstable_Suspense>
602 + <LazyFoo />
603 + </React.unstable_Suspense>
604 + </Parent>,
605 + );
606 + ReactNoop.flush();
607 + expect(getFlameChart()).toMatchSnapshot();
608 + });
609 +
610 it('does not schedule an extra callback if setState is called during a synchronous commit phase', () => {
611 class Component extends React.Component {
612 state = {step: 1};
packages/react-reconciler/src/__tests__/__snapshots__/ReactIncrementalPerf-test.internal.js.snap
+48
@@ -362,6 +362,40 @@ exports[`ReactDebugFiberPerf skips parents during setState 1`] = `
362 "
363 `;
364
365 +exports[`ReactDebugFiberPerf supports Suspense and lazy 1`] = `
366 +"⚛ (Waiting for async callback... will force flush in 5250 ms)
367 +
368 +⚛ (React Tree Reconciliation: Completed Root)
369 + ⚛ Parent [mount]
370 + ⛔ Suspense [mount] Warning: Rendering was suspended
371 + ⚛ Suspense [mount]
372 + ⚛ Spinner [mount]
373 +"
374 +`;
375 +
376 +exports[`ReactDebugFiberPerf supports Suspense and lazy 2`] = `
377 +"⚛ (Waiting for async callback... will force flush in 5250 ms)
378 +
379 +⚛ (React Tree Reconciliation: Completed Root)
380 + ⚛ Parent [mount]
381 + ⛔ Suspense [mount] Warning: Rendering was suspended
382 + ⚛ Suspense [mount]
383 + ⚛ Spinner [mount]
384 +
385 +⚛ (Waiting for async callback... will force flush in 5250 ms)
386 +
387 +⚛ (React Tree Reconciliation: Completed Root)
388 + ⚛ Parent [mount]
389 + ⚛ Suspense [mount]
390 + ⚛ Foo [mount]
391 +
392 +⚛ (Committing Changes)
393 + ⚛ (Committing Snapshot Effects: 0 Total)
394 + ⚛ (Committing Host Effects: 1 Total)
395 + ⚛ (Calling Lifecycle Methods: 0 Total)
396 +"
397 +`;
398 +
399 exports[`ReactDebugFiberPerf supports portals 1`] = `
400 "⚛ (Waiting for async callback... will force flush in 5250 ms)
401
@@ -376,6 +410,20 @@ exports[`ReactDebugFiberPerf supports portals 1`] = `
410 "
411 `;
412
413 +exports[`ReactDebugFiberPerf supports pure 1`] = `
414 +"⚛ (Waiting for async callback... will force flush in 5250 ms)
415 +
416 +⚛ (React Tree Reconciliation: Completed Root)
417 + ⚛ Parent [mount]
418 + ⚛ Pure(Foo) [mount]
419 +
420 +⚛ (Committing Changes)
421 + ⚛ (Committing Snapshot Effects: 0 Total)
422 + ⚛ (Committing Host Effects: 1 Total)
423 + ⚛ (Calling Lifecycle Methods: 0 Total)
424 +"
425 +`;
426 +
427 exports[`ReactDebugFiberPerf warns if an in-progress update is interrupted 1`] = `
428 "⚛ (Waiting for async callback... will force flush in 5250 ms)
429
packages/shared/getComponentName.js
+16 -6
@@ -16,6 +16,7 @@ import {
16 REACT_FORWARD_REF_TYPE,
17 REACT_FRAGMENT_TYPE,
18 REACT_PORTAL_TYPE,
19 + REACT_PURE_TYPE,
20 REACT_PROFILER_TYPE,
21 REACT_PROVIDER_TYPE,
22 REACT_STRICT_MODE_TYPE,
@@ -23,6 +24,18 @@ import {
24 } from 'shared/ReactSymbols';
25 import {refineResolvedThenable} from 'shared/ReactLazyComponent';
26
27 +function getWrappedName(
28 + outerType: mixed,
29 + innerType: any,
30 + wrapperName: string,
31 +): string {
32 + const functionName = innerType.displayName || innerType.name || '';
33 + return (
34 + (outerType: any).displayName ||
35 + (functionName !== '' ? `${wrapperName}(${functionName})` : wrapperName)
36 + );
37 +}
38 +
39 function getComponentName(type: mixed): string | null {
40 if (type == null) {
41 // Host root, text node or just invalid type.
@@ -64,12 +77,9 @@ function getComponentName(type: mixed): string | null {
77 case REACT_PROVIDER_TYPE:
78 return 'Context.Provider';
79 case REACT_FORWARD_REF_TYPE:
67 - const renderFn = (type.render: any);
68 - const functionName = renderFn.displayName || renderFn.name || '';
69 - return (
70 - (type: any).displayName ||
71 - (functionName !== '' ? `ForwardRef(${functionName})` : 'ForwardRef')
72 - );
80 + return getWrappedName(type, type.render, 'ForwardRef');
81 + case REACT_PURE_TYPE:
82 + return getWrappedName(type, type.render, 'Pure');
83 }
84 if (typeof type.then === 'function') {
85 const thenable: Thenable<mixed> = (type: any);