@samitouri / QOS-React-2 / commits / 9a35adc96d

Only call Profiler onRender when a descendant had work (#17223)

Brian Vaughn committed Oct 30, 2019 at 11:08 UTC 9a35adc96d6ff99de779aee52bb78d3c24f86c1d
2 files changed +95 -27
packages/react-reconciler/src/ReactFiberBeginWork.js
+6 -1
@@ -2893,7 +2893,12 @@ function beginWork(
2893 }
2894 case Profiler:
2895 if (enableProfilerTimer) {
2896 - workInProgress.effectTag |= Update;
2896 + // Profiler should only call onRender when one of its descendants actually rendered.
2897 + const hasChildWork =
2898 + workInProgress.childExpirationTime >= renderExpirationTime;
2899 + if (hasChildWork) {
2900 + workInProgress.effectTag |= Update;
2901 + }
2902 }
2903 break;
2904 case SuspenseComponent: {
packages/react/src/__tests__/ReactProfiler-test.internal.js
+89 -26
@@ -295,6 +295,79 @@ describe('Profiler', () => {
295 );
296 });
297
298 + it('does not report work done on a sibling', () => {
299 + const callback = jest.fn();
300 +
301 + const DoesNotUpdate = React.memo(function DoesNotUpdateInner() {
302 + Scheduler.unstable_advanceTime(10);
303 + return null;
304 + }, () => true);
305 +
306 + let updateProfilerSibling;
307 +
308 + function ProfilerSibling() {
309 + const [count, setCount] = React.useState(0);
310 + updateProfilerSibling = () => setCount(count + 1);
311 + return null;
312 + }
313 +
314 + function App() {
315 + return (
316 + <React.Fragment>
317 + <React.Profiler id="test" onRender={callback}>
318 + <DoesNotUpdate />
319 + </React.Profiler>
320 + <ProfilerSibling />
321 + </React.Fragment>
322 + );
323 + }
324 +
325 + const renderer = ReactTestRenderer.create(<App />);
326 +
327 + expect(callback).toHaveBeenCalledTimes(1);
328 +
329 + let call = callback.mock.calls[0];
330 +
331 + expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6);
332 + expect(call[0]).toBe('test');
333 + expect(call[1]).toBe('mount');
334 + expect(call[2]).toBe(10); // actual time
335 + expect(call[3]).toBe(10); // base time
336 + expect(call[4]).toBe(0); // start time
337 + expect(call[5]).toBe(10); // commit time
338 + expect(call[6]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
339 +
340 + callback.mockReset();
341 +
342 + Scheduler.unstable_advanceTime(20); // 10 -> 30
343 +
344 + // Updating a parent should report a re-render,
345 + // since React technically did a little bit of work between the Profiler and the bailed out subtree.
346 + renderer.update(<App />);
347 +
348 + expect(callback).toHaveBeenCalledTimes(1);
349 +
350 + call = callback.mock.calls[0];
351 +
352 + expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6);
353 + expect(call[0]).toBe('test');
354 + expect(call[1]).toBe('update');
355 + expect(call[2]).toBe(0); // actual time
356 + expect(call[3]).toBe(10); // base time
357 + expect(call[4]).toBe(30); // start time
358 + expect(call[5]).toBe(30); // commit time
359 + expect(call[6]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
360 +
361 + callback.mockReset();
362 +
363 + Scheduler.unstable_advanceTime(20); // 30 -> 50
364 +
365 + // Updating a sibling should not report a re-render.
366 + ReactTestRenderer.act(updateProfilerSibling);
367 +
368 + expect(callback).not.toHaveBeenCalled();
369 + });
370 +
371 it('logs render times for both mount and update', () => {
372 const callback = jest.fn();
373
@@ -372,7 +445,7 @@ describe('Profiler', () => {
445 Scheduler.unstable_advanceTime(5); // 0 -> 5
446
447 ReactTestRenderer.create(
375 - <>
448 + <React.Fragment>
449 <React.Profiler id="parent" onRender={callback}>
450 <AdvanceTime byAmount={10}>
451 <React.Profiler id="child" onRender={callback}>
@@ -380,7 +453,7 @@ describe('Profiler', () => {
453 </React.Profiler>
454 </AdvanceTime>
455 </React.Profiler>
383 - </>,
456 + </React.Fragment>,
457 );
458
459 expect(callback).toHaveBeenCalledTimes(2);
@@ -407,14 +480,14 @@ describe('Profiler', () => {
480 Scheduler.unstable_advanceTime(5); // 0 -> 5
481
482 ReactTestRenderer.create(
410 - <>
483 + <React.Fragment>
484 <React.Profiler id="first" onRender={callback}>
485 <AdvanceTime byAmount={20} />
486 </React.Profiler>
487 <React.Profiler id="second" onRender={callback}>
488 <AdvanceTime byAmount={5} />
489 </React.Profiler>
417 - </>,
490 + </React.Fragment>,
491 );
492
493 expect(callback).toHaveBeenCalledTimes(2);
@@ -440,13 +513,13 @@ describe('Profiler', () => {
513 Scheduler.unstable_advanceTime(5); // 0 -> 5
514
515 ReactTestRenderer.create(
443 - <>
516 + <React.Fragment>
517 <AdvanceTime byAmount={20} />
518 <React.Profiler id="test" onRender={callback}>
519 <AdvanceTime byAmount={5} />
520 </React.Profiler>
521 <AdvanceTime byAmount={20} />
449 - </>,
522 + </React.Fragment>,
523 );
524
525 expect(callback).toHaveBeenCalledTimes(1);
@@ -471,28 +544,18 @@ describe('Profiler', () => {
544 }
545 }
546
474 - class Pure extends React.PureComponent {
475 - render() {
476 - return this.props.children;
477 - }
478 - }
479 -
547 const renderer = ReactTestRenderer.create(
548 <React.Profiler id="outer" onRender={callback}>
549 <Updater>
483 - <React.Profiler id="middle" onRender={callback}>
484 - <Pure>
485 - <React.Profiler id="inner" onRender={callback}>
486 - <div />
487 - </React.Profiler>
488 - </Pure>
550 + <React.Profiler id="inner" onRender={callback}>
551 + <div />
552 </React.Profiler>
553 </Updater>
554 </React.Profiler>,
555 );
556
557 // All profile callbacks are called for initial render
495 - expect(callback).toHaveBeenCalledTimes(3);
558 + expect(callback).toHaveBeenCalledTimes(2);
559
560 callback.mockReset();
561
@@ -502,11 +565,11 @@ describe('Profiler', () => {
565 });
566 });
567
505 - // Only call profile updates for paths that have re-rendered
506 - // Since "inner" is beneath a pure component, it isn't called
507 - expect(callback).toHaveBeenCalledTimes(2);
508 - expect(callback.mock.calls[0][0]).toBe('middle');
509 - expect(callback.mock.calls[1][0]).toBe('outer');
568 + // Only call onRender for paths that have re-rendered.
569 + // Since the Updater's props didn't change,
570 + // React does not re-render its children.
571 + expect(callback).toHaveBeenCalledTimes(1);
572 + expect(callback.mock.calls[0][0]).toBe('outer');
573 });
574
575 it('decreases actual time but not base time when sCU prevents an update', () => {
@@ -1455,11 +1518,11 @@ describe('Profiler', () => {
1518 render() {
1519 instance = this;
1520 return (
1458 - <>
1521 + <React.Fragment>
1522 <Yield value="first" />
1523 {this.state.count}
1524 <Yield value="last" />
1462 - </>
1525 + </React.Fragment>
1526 );
1527 }
1528 }