Skip to content

Commit 8b2d378

Browse files
authored
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.
1 parent 50d9451 commit 8b2d378

File tree

3 files changed

+60
-80
lines changed

3 files changed

+60
-80
lines changed

packages/react-reconciler/src/ReactFiberBeginWork.new.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import {
6161
ContentReset,
6262
DidCapture,
6363
Update,
64+
Passive,
6465
Ref,
6566
Deletion,
6667
ForceUpdateForLegacySuspense,
@@ -674,7 +675,8 @@ function updateProfiler(
674675
renderLanes: Lanes,
675676
) {
676677
if (enableProfilerTimer) {
677-
workInProgress.flags |= Update;
678+
// TODO: Only call onRender et al if subtree has effects
679+
workInProgress.flags |= Update | Passive;
678680

679681
// Reset effect durations for the next eventual effect phase.
680682
// These are reset during render to allow the DevTools commit hook a chance to read them,
@@ -3116,12 +3118,13 @@ function beginWork(
31163118
case Profiler:
31173119
if (enableProfilerTimer) {
31183120
// Profiler should only call onRender when one of its descendants actually rendered.
3121+
// TODO: Only call onRender et al if subtree has effects
31193122
const hasChildWork = includesSomeLane(
31203123
renderLanes,
31213124
workInProgress.childLanes,
31223125
);
31233126
if (hasChildWork) {
3124-
workInProgress.flags |= Update;
3127+
workInProgress.flags |= Passive | Update;
31253128
}
31263129

31273130
// Reset effect durations for the next eventual effect phase.

packages/react-reconciler/src/ReactFiberCommitWork.new.js

+51-49
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,6 @@ import {
121121
captureCommitPhaseError,
122122
resolveRetryWakeable,
123123
markCommitTimeOfFallback,
124-
enqueuePendingPassiveProfilerEffect,
125124
schedulePassiveEffectCallback,
126125
} from './ReactFiberWorkLoop.new';
127126
import {
@@ -507,57 +506,55 @@ function commitHookEffectListMount2(fiber: Fiber): void {
507506
}
508507
}
509508

510-
export function commitPassiveEffectDurations(
509+
function commitProfilerPassiveEffect(
511510
finishedRoot: FiberRoot,
512511
finishedWork: Fiber,
513512
): void {
514513
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+
);
543539
}
540+
}
544541

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;
555551
}
556-
break;
552+
parentFiber = parentFiber.return;
557553
}
558-
default:
559-
break;
554+
break;
560555
}
556+
default:
557+
break;
561558
}
562559
}
563560
}
@@ -841,13 +838,9 @@ function commitLifeCycles(
841838
}
842839
}
843840

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-
849841
// Propagate layout effect durations to the next nearest Profiler ancestor.
850842
// Do not reset these values until the next render so DevTools has a chance to read them first.
843+
// TODO: Use JS Stack instead
851844
let parentFiber = finishedWork.return;
852845
while (parentFiber !== null) {
853846
if (parentFiber.tag === Profiler) {
@@ -1912,6 +1905,7 @@ function commitPassiveWork(finishedWork: Fiber): void {
19121905
finishedWork,
19131906
finishedWork.return,
19141907
);
1908+
break;
19151909
}
19161910
}
19171911
}
@@ -1933,13 +1927,21 @@ function commitPassiveUnmount(
19331927
}
19341928
}
19351929

1936-
function commitPassiveLifeCycles(finishedWork: Fiber): void {
1930+
function commitPassiveLifeCycles(
1931+
finishedRoot: FiberRoot,
1932+
finishedWork: Fiber,
1933+
): void {
19371934
switch (finishedWork.tag) {
19381935
case FunctionComponent:
19391936
case ForwardRef:
19401937
case SimpleMemoComponent:
19411938
case Block: {
19421939
commitHookEffectListMount2(finishedWork);
1940+
break;
1941+
}
1942+
case Profiler: {
1943+
commitProfilerPassiveEffect(finishedRoot, finishedWork);
1944+
break;
19431945
}
19441946
}
19451947
}

packages/react-reconciler/src/ReactFiberWorkLoop.new.js

+4-29
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import {
2121
enableSuspenseServerRenderer,
2222
replayFailedUnitOfWorkWithInvokeGuardedCallback,
2323
enableProfilerTimer,
24-
enableProfilerCommitHooks,
2524
enableSchedulerTracing,
2625
warnAboutUnmockedScheduler,
2726
deferRenderPhaseUpdateToNextBatch,
@@ -197,7 +196,6 @@ import {
197196
commitPassiveLifeCycles as commitPassiveEffectOnFiber,
198197
commitDetachRef,
199198
commitAttachRef,
200-
commitPassiveEffectDurations,
201199
commitResetTextContent,
202200
isSuspenseBoundaryBeingHidden,
203201
} from './ReactFiberCommitWork.new';
@@ -336,7 +334,6 @@ let rootDoesHavePassiveEffects: boolean = false;
336334
let rootWithPendingPassiveEffects: FiberRoot | null = null;
337335
let pendingPassiveEffectsRenderPriority: ReactPriorityLevel = NoSchedulerPriority;
338336
let pendingPassiveEffectsLanes: Lanes = NoLanes;
339-
let pendingPassiveProfilerEffects: Array<Fiber> = [];
340337

341338
let rootsWithPendingDiscreteUpdates: Set<FiberRoot> | null = null;
342339

@@ -2451,31 +2448,18 @@ export function flushPassiveEffects(): boolean {
24512448
return false;
24522449
}
24532450

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 {
24682452
let fiber = firstChild;
24692453
while (fiber !== null) {
24702454
const primarySubtreeFlags = fiber.subtreeFlags & PassiveMask;
24712455

24722456
if (fiber.child !== null && primarySubtreeFlags !== NoFlags) {
2473-
flushPassiveMountEffects(fiber.child);
2457+
flushPassiveMountEffects(root, fiber.child);
24742458
}
24752459

24762460
if ((fiber.flags & Passive) !== NoFlags) {
24772461
setCurrentDebugFiberInDEV(fiber);
2478-
commitPassiveEffectOnFiber(fiber);
2462+
commitPassiveEffectOnFiber(root, fiber);
24792463
resetCurrentDebugFiberInDEV();
24802464
}
24812465

@@ -2586,16 +2570,7 @@ function flushPassiveEffectsImpl() {
25862570
// value set by a create function in another component.
25872571
// Layout effects have the same constraint.
25882572
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);
25992574

26002575
if (enableSchedulerTracing) {
26012576
popInteractions(((prevInteractions: any): Set<Interaction>));

0 commit comments

Comments
 (0)