Use Passive flag to schedule onPostCommit (#19862)
Instead of calling `onPostCommit` in a separate phase, we can fire them during the same traversal as the rest of the passive effects. This works because effects are executed depth-first. So by the time we reach a Profiler node, we'll have already executed all the effects in its subtree.
Andrew Clark committed
Sep 18, 2020 at 15:02 UTC
8b2d3783e58d1acea53428a10d2035a8399060fe
3 files changed
+60
-80
packages/react-reconciler/src/ReactFiberBeginWork.new.js
+5
-2
@@ -61,6 +61,7 @@ import {
61
ContentReset,
62
DidCapture,
63
Update,
64
+ Passive,
65
Ref,
66
Deletion,
67
ForceUpdateForLegacySuspense,
@@ -674,7 +675,8 @@ function updateProfiler(
675
renderLanes: Lanes,
676
) {
677
if (enableProfilerTimer) {
677
- workInProgress.flags |= Update;
678
+ // TODO: Only call onRender et al if subtree has effects
679
+ workInProgress.flags |= Update | Passive;
680
681
// Reset effect durations for the next eventual effect phase.
682
// These are reset during render to allow the DevTools commit hook a chance to read them,
@@ -3116,12 +3118,13 @@ function beginWork(
3118
case Profiler:
3119
if (enableProfilerTimer) {
3120
// Profiler should only call onRender when one of its descendants actually rendered.
3121
+ // TODO: Only call onRender et al if subtree has effects
3122
const hasChildWork = includesSomeLane(
3123
renderLanes,
3124
workInProgress.childLanes,
3125
);
3126
if (hasChildWork) {
3124
- workInProgress.flags |= Update;
3127
+ workInProgress.flags |= Passive | Update;
3128
}
3129
3130
// Reset effect durations for the next eventual effect phase.
packages/react-reconciler/src/ReactFiberCommitWork.new.js
+51
-49
@@ -121,7 +121,6 @@ import {
121
captureCommitPhaseError,
122
resolveRetryWakeable,
123
markCommitTimeOfFallback,
124
- enqueuePendingPassiveProfilerEffect,
124
schedulePassiveEffectCallback,
125
} from './ReactFiberWorkLoop.new';
126
import {
@@ -507,57 +506,55 @@ function commitHookEffectListMount2(fiber: Fiber): void {
506
}
507
}
508
510
-export function commitPassiveEffectDurations(
509
+function commitProfilerPassiveEffect(
510
finishedRoot: FiberRoot,
511
finishedWork: Fiber,
512
): void {
513
if (enableProfilerTimer && enableProfilerCommitHooks) {
515
- // Only Profilers with work in their subtree will have an Update effect scheduled.
516
- if ((finishedWork.flags & Update) !== NoFlags) {
517
- switch (finishedWork.tag) {
518
- case Profiler: {
519
- const {passiveEffectDuration} = finishedWork.stateNode;
520
- const {id, onPostCommit} = finishedWork.memoizedProps;
521
-
522
- // This value will still reflect the previous commit phase.
523
- // It does not get reset until the start of the next commit phase.
524
- const commitTime = getCommitTime();
525
-
526
- if (typeof onPostCommit === 'function') {
527
- if (enableSchedulerTracing) {
528
- onPostCommit(
529
- id,
530
- finishedWork.alternate === null ? 'mount' : 'update',
531
- passiveEffectDuration,
532
- commitTime,
533
- finishedRoot.memoizedInteractions,
534
- );
535
- } else {
536
- onPostCommit(
537
- id,
538
- finishedWork.alternate === null ? 'mount' : 'update',
539
- passiveEffectDuration,
540
- commitTime,
541
- );
542
- }
514
+ switch (finishedWork.tag) {
515
+ case Profiler: {
516
+ const {passiveEffectDuration} = finishedWork.stateNode;
517
+ const {id, onPostCommit} = finishedWork.memoizedProps;
518
+
519
+ // This value will still reflect the previous commit phase.
520
+ // It does not get reset until the start of the next commit phase.
521
+ const commitTime = getCommitTime();
522
+
523
+ if (typeof onPostCommit === 'function') {
524
+ if (enableSchedulerTracing) {
525
+ onPostCommit(
526
+ id,
527
+ finishedWork.alternate === null ? 'mount' : 'update',
528
+ passiveEffectDuration,
529
+ commitTime,
530
+ finishedRoot.memoizedInteractions,
531
+ );
532
+ } else {
533
+ onPostCommit(
534
+ id,
535
+ finishedWork.alternate === null ? 'mount' : 'update',
536
+ passiveEffectDuration,
537
+ commitTime,
538
+ );
539
}
540
+ }
541
545
- // Bubble times to the next nearest ancestor Profiler.
546
- // After we process that Profiler, we'll bubble further up.
547
- let parentFiber = finishedWork.return;
548
- while (parentFiber !== null) {
549
- if (parentFiber.tag === Profiler) {
550
- const parentStateNode = parentFiber.stateNode;
551
- parentStateNode.passiveEffectDuration += passiveEffectDuration;
552
- break;
553
- }
554
- parentFiber = parentFiber.return;
542
+ // Bubble times to the next nearest ancestor Profiler.
543
+ // After we process that Profiler, we'll bubble further up.
544
+ // TODO: Use JS Stack instead
545
+ let parentFiber = finishedWork.return;
546
+ while (parentFiber !== null) {
547
+ if (parentFiber.tag === Profiler) {
548
+ const parentStateNode = parentFiber.stateNode;
549
+ parentStateNode.passiveEffectDuration += passiveEffectDuration;
550
+ break;
551
}
556
- break;
552
+ parentFiber = parentFiber.return;
553
}
558
- default:
559
- break;
554
+ break;
555
}
556
+ default:
557
+ break;
558
}
559
}
560
}
@@ -841,13 +838,9 @@ function commitLifeCycles(
838
}
839
}
840
844
- // Schedule a passive effect for this Profiler to call onPostCommit hooks.
845
- // This effect should be scheduled even if there is no onPostCommit callback for this Profiler,
846
- // because the effect is also where times bubble to parent Profilers.
847
- enqueuePendingPassiveProfilerEffect(finishedWork);
848
-
841
// Propagate layout effect durations to the next nearest Profiler ancestor.
842
// Do not reset these values until the next render so DevTools has a chance to read them first.
843
+ // TODO: Use JS Stack instead
844
let parentFiber = finishedWork.return;
845
while (parentFiber !== null) {
846
if (parentFiber.tag === Profiler) {
@@ -1912,6 +1905,7 @@ function commitPassiveWork(finishedWork: Fiber): void {
1905
finishedWork,
1906
finishedWork.return,
1907
);
1908
+ break;
1909
}
1910
}
1911
}
@@ -1933,13 +1927,21 @@ function commitPassiveUnmount(
1927
}
1928
}
1929
1936
-function commitPassiveLifeCycles(finishedWork: Fiber): void {
1930
+function commitPassiveLifeCycles(
1931
+ finishedRoot: FiberRoot,
1932
+ finishedWork: Fiber,
1933
+): void {
1934
switch (finishedWork.tag) {
1935
case FunctionComponent:
1936
case ForwardRef:
1937
case SimpleMemoComponent:
1938
case Block: {
1939
commitHookEffectListMount2(finishedWork);
1940
+ break;
1941
+ }
1942
+ case Profiler: {
1943
+ commitProfilerPassiveEffect(finishedRoot, finishedWork);
1944
+ break;
1945
}
1946
}
1947
}
packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+4
-29
@@ -21,7 +21,6 @@ import {
21
enableSuspenseServerRenderer,
22
replayFailedUnitOfWorkWithInvokeGuardedCallback,
23
enableProfilerTimer,
24
- enableProfilerCommitHooks,
24
enableSchedulerTracing,
25
warnAboutUnmockedScheduler,
26
deferRenderPhaseUpdateToNextBatch,
@@ -197,7 +196,6 @@ import {
196
commitPassiveLifeCycles as commitPassiveEffectOnFiber,
197
commitDetachRef,
198
commitAttachRef,
200
- commitPassiveEffectDurations,
199
commitResetTextContent,
200
isSuspenseBoundaryBeingHidden,
201
} from './ReactFiberCommitWork.new';
@@ -336,7 +334,6 @@ let rootDoesHavePassiveEffects: boolean = false;
334
let rootWithPendingPassiveEffects: FiberRoot | null = null;
335
let pendingPassiveEffectsRenderPriority: ReactPriorityLevel = NoSchedulerPriority;
336
let pendingPassiveEffectsLanes: Lanes = NoLanes;
339
-let pendingPassiveProfilerEffects: Array<Fiber> = [];
337
338
let rootsWithPendingDiscreteUpdates: Set<FiberRoot> | null = null;
339
@@ -2451,31 +2448,18 @@ export function flushPassiveEffects(): boolean {
2448
return false;
2449
}
2450
2454
-export function enqueuePendingPassiveProfilerEffect(fiber: Fiber): void {
2455
- if (enableProfilerTimer && enableProfilerCommitHooks) {
2456
- pendingPassiveProfilerEffects.push(fiber);
2457
- if (!rootDoesHavePassiveEffects) {
2458
- rootDoesHavePassiveEffects = true;
2459
- scheduleCallback(NormalSchedulerPriority, () => {
2460
- flushPassiveEffects();
2461
- return null;
2462
- });
2463
- }
2464
- }
2465
-}
2466
-
2467
-function flushPassiveMountEffects(firstChild: Fiber): void {
2451
+function flushPassiveMountEffects(root, firstChild: Fiber): void {
2452
let fiber = firstChild;
2453
while (fiber !== null) {
2454
const primarySubtreeFlags = fiber.subtreeFlags & PassiveMask;
2455
2456
if (fiber.child !== null && primarySubtreeFlags !== NoFlags) {
2473
- flushPassiveMountEffects(fiber.child);
2457
+ flushPassiveMountEffects(root, fiber.child);
2458
}
2459
2460
if ((fiber.flags & Passive) !== NoFlags) {
2461
setCurrentDebugFiberInDEV(fiber);
2478
- commitPassiveEffectOnFiber(fiber);
2462
+ commitPassiveEffectOnFiber(root, fiber);
2463
resetCurrentDebugFiberInDEV();
2464
}
2465
@@ -2586,16 +2570,7 @@ function flushPassiveEffectsImpl() {
2570
// value set by a create function in another component.
2571
// Layout effects have the same constraint.
2572
flushPassiveUnmountEffects(root.current);
2589
- flushPassiveMountEffects(root.current);
2590
-
2591
- if (enableProfilerTimer && enableProfilerCommitHooks) {
2592
- const profilerEffects = pendingPassiveProfilerEffects;
2593
- pendingPassiveProfilerEffects = [];
2594
- for (let i = 0; i < profilerEffects.length; i++) {
2595
- const fiber = ((profilerEffects[i]: any): Fiber);
2596
- commitPassiveEffectDurations(root, fiber);
2597
- }
2598
- }
2573
+ flushPassiveMountEffects(root, root.current);
2574
2575
if (enableSchedulerTracing) {
2576
popInteractions(((prevInteractions: any): Set<Interaction>));