@samitouri / QOS-React-2 / commits / 87c023b1c1

Profiler onRender only called when we do work (#19885)

If we didn't perform any work in the subtree, skip calling onRender.

Brian Vaughn committed Sep 22, 2020 at 14:47 UTC 87c023b1c1b00d6776b7031f6e105913ead355da
4 files changed +54 -28
packages/react-reconciler/src/ReactFiberBeginWork.new.js
+3
@@ -1148,6 +1148,9 @@ function updateHostComponent(
1148 workInProgress.flags |= ContentReset;
1149 }
1150
1151 + // React DevTools reads this flag.
1152 + workInProgress.flags |= PerformedWork;
1153 +
1154 markRef(current, workInProgress);
1155 reconcileChildren(current, workInProgress, nextChildren, renderLanes);
1156 return workInProgress.child;
packages/react-reconciler/src/ReactFiberCompleteWork.new.js
+8 -3
@@ -77,6 +77,7 @@ import {
77 LayoutMask,
78 PassiveMask,
79 StaticMask,
80 + PerformedWork,
81 } from './ReactFiberFlags';
82 import invariant from 'shared/invariant';
83
@@ -987,9 +988,13 @@ function completeWork(
988 const flags = workInProgress.flags;
989 let newFlags = flags;
990
990 - // Call onRender any time this fiber or its subtree are worked on, even
991 - // if there are no effects
992 - newFlags |= OnRenderFlag;
991 + // Call onRender any time this fiber or its subtree are worked on.
992 + if (
993 + (flags & PerformedWork) !== NoFlags ||
994 + (subtreeFlags & PerformedWork) !== NoFlags
995 + ) {
996 + newFlags |= OnRenderFlag;
997 + }
998
999 // Call onCommit only if the subtree contains layout work, or if it
1000 // contains deletions, since those might result in unmount work, which
packages/react/src/__tests__/ReactDOMTracing-test.internal.js
+21 -12
@@ -151,12 +151,17 @@ describe('ReactDOMTracing', () => {
151 expect(
152 onInteractionScheduledWorkCompleted,
153 ).toHaveBeenLastNotifiedOfInteraction(interaction);
154 - // TODO: This is 4 instead of 3 because this update was scheduled at
155 - // idle priority, and idle updates are slightly higher priority than
156 - // offscreen work. So it takes two render passes to finish it. Profiler
157 - // calls `onRender` for the first render even though everything
158 - // bails out.
159 - expect(onRender).toHaveBeenCalledTimes(4);
154 +
155 + if (gate(flags => flags.new)) {
156 + expect(onRender).toHaveBeenCalledTimes(3);
157 + } else {
158 + // TODO: This is 4 instead of 3 because this update was scheduled at
159 + // idle priority, and idle updates are slightly higher priority than
160 + // offscreen work. So it takes two render passes to finish it. Profiler
161 + // calls `onRender` for the first render even though everything
162 + // bails out.
163 + expect(onRender).toHaveBeenCalledTimes(4);
164 + }
165 expect(onRender).toHaveLastRenderedWithInteractions(
166 new Set([interaction]),
167 );
@@ -305,12 +310,16 @@ describe('ReactDOMTracing', () => {
310 expect(
311 onInteractionScheduledWorkCompleted,
312 ).toHaveBeenLastNotifiedOfInteraction(interaction);
308 - // TODO: This is 4 instead of 3 because this update was scheduled at
309 - // idle priority, and idle updates are slightly higher priority than
310 - // offscreen work. So it takes two render passes to finish it. Profiler
311 - // calls `onRender` for the first render even though everything
312 - // bails out.
313 - expect(onRender).toHaveBeenCalledTimes(4);
313 + if (gate(flags => flags.new)) {
314 + expect(onRender).toHaveBeenCalledTimes(3);
315 + } else {
316 + // TODO: This is 4 instead of 3 because this update was scheduled at
317 + // idle priority, and idle updates are slightly higher priority than
318 + // offscreen work. So it takes two render passes to finish it. Profiler
319 + // calls `onRender` for the first render even though everything
320 + // bails out.
321 + expect(onRender).toHaveBeenCalledTimes(4);
322 + }
323 expect(onRender).toHaveLastRenderedWithInteractions(
324 new Set([interaction]),
325 );
packages/react/src/__tests__/ReactProfiler-test.internal.js
+22 -13
@@ -362,24 +362,33 @@ describe('Profiler', () => {
362
363 Scheduler.unstable_advanceTime(20); // 10 -> 30
364
365 - // Updating a parent should report a re-render,
366 - // since React technically did a little bit of work between the Profiler and the bailed out subtree.
365 renderer.update(<App />);
366
369 - expect(callback).toHaveBeenCalledTimes(1);
367 + if (gate(flags => flags.new)) {
368 + // None of the Profiler's subtree was rendered because App bailed out before the Profiler.
369 + // So we expect onRender not to be called.
370 + expect(callback).not.toHaveBeenCalled();
371 + } else {
372 + // Updating a parent reports a re-render,
373 + // since React technically did a little bit of work between the Profiler and the bailed out subtree.
374 + // This is not optimal but it's how the old reconciler fork works.
375 + expect(callback).toHaveBeenCalledTimes(1);
376
371 - call = callback.mock.calls[0];
377 + call = callback.mock.calls[0];
378
373 - expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6);
374 - expect(call[0]).toBe('test');
375 - expect(call[1]).toBe('update');
376 - expect(call[2]).toBe(0); // actual time
377 - expect(call[3]).toBe(10); // base time
378 - expect(call[4]).toBe(30); // start time
379 - expect(call[5]).toBe(30); // commit time
380 - expect(call[6]).toEqual(enableSchedulerTracing ? new Set() : undefined); // interaction events
379 + expect(call).toHaveLength(enableSchedulerTracing ? 7 : 6);
380 + expect(call[0]).toBe('test');
381 + expect(call[1]).toBe('update');
382 + expect(call[2]).toBe(0); // actual time
383 + expect(call[3]).toBe(10); // base time
384 + expect(call[4]).toBe(30); // start time
385 + expect(call[5]).toBe(30); // commit time
386 + expect(call[6]).toEqual(
387 + enableSchedulerTracing ? new Set() : undefined,
388 + ); // interaction events
389
382 - callback.mockReset();
390 + callback.mockReset();
391 + }
392
393 Scheduler.unstable_advanceTime(20); // 30 -> 50
394