@samitouri / QOS-React-2 / commits / 8b2d3783e5

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>));