@samitouri / QOS-React-2 / commits / 6132919bf2

Convert layout phase to depth-first traversal (#20595)

Andrew Clark committed Jan 15, 2021 at 14:22 UTC 6132919bf2b8851382547b34a442e7e0c09c5697
5 files changed +368 -328
packages/react-reconciler/src/ReactFiberCommitWork.new.js
+342 -249
@@ -69,8 +69,11 @@ import {
69 ChildDeletion,
70 Snapshot,
71 Update,
72 + Callback,
73 + Ref,
74 Passive,
75 PassiveMask,
76 + LayoutMask,
77 PassiveUnmountPendingDev,
78 } from './ReactFiberFlags';
79 import getComponentName from 'shared/getComponentName';
@@ -490,95 +493,154 @@ export function commitPassiveEffectDurations(
493 }
494 }
495
493 -function commitLifeCycles(
496 +function commitLayoutEffectOnFiber(
497 finishedRoot: FiberRoot,
498 current: Fiber | null,
499 finishedWork: Fiber,
500 committedLanes: Lanes,
501 ): void {
499 - switch (finishedWork.tag) {
500 - case FunctionComponent:
501 - case ForwardRef:
502 - case SimpleMemoComponent: {
503 - // At this point layout effects have already been destroyed (during mutation phase).
504 - // This is done to prevent sibling component effects from interfering with each other,
505 - // e.g. a destroy function in one component should never override a ref set
506 - // by a create function in another component during the same commit.
507 - if (
508 - enableProfilerTimer &&
509 - enableProfilerCommitHooks &&
510 - finishedWork.mode & ProfileMode
511 - ) {
512 - try {
513 - startLayoutEffectTimer();
502 + if ((finishedWork.flags & (Update | Callback)) !== NoFlags) {
503 + switch (finishedWork.tag) {
504 + case FunctionComponent:
505 + case ForwardRef:
506 + case SimpleMemoComponent: {
507 + // At this point layout effects have already been destroyed (during mutation phase).
508 + // This is done to prevent sibling component effects from interfering with each other,
509 + // e.g. a destroy function in one component should never override a ref set
510 + // by a create function in another component during the same commit.
511 + if (
512 + enableProfilerTimer &&
513 + enableProfilerCommitHooks &&
514 + finishedWork.mode & ProfileMode
515 + ) {
516 + try {
517 + startLayoutEffectTimer();
518 + commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
519 + } finally {
520 + recordLayoutEffectDuration(finishedWork);
521 + }
522 + } else {
523 commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
515 - } finally {
516 - recordLayoutEffectDuration(finishedWork);
524 }
518 - } else {
519 - commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
520 - }
525
522 - schedulePassiveEffects(finishedWork);
523 - return;
524 - }
525 - case ClassComponent: {
526 - const instance = finishedWork.stateNode;
527 - if (finishedWork.flags & Update) {
528 - if (current === null) {
529 - // We could update instance props and state here,
530 - // but instead we rely on them being set during last render.
531 - // TODO: revisit this when we implement resuming.
532 - if (__DEV__) {
526 + schedulePassiveEffects(finishedWork);
527 + break;
528 + }
529 + case ClassComponent: {
530 + const instance = finishedWork.stateNode;
531 + if (finishedWork.flags & Update) {
532 + if (current === null) {
533 + // We could update instance props and state here,
534 + // but instead we rely on them being set during last render.
535 + // TODO: revisit this when we implement resuming.
536 + if (__DEV__) {
537 + if (
538 + finishedWork.type === finishedWork.elementType &&
539 + !didWarnAboutReassigningProps
540 + ) {
541 + if (instance.props !== finishedWork.memoizedProps) {
542 + console.error(
543 + 'Expected %s props to match memoized props before ' +
544 + 'componentDidMount. ' +
545 + 'This might either be because of a bug in React, or because ' +
546 + 'a component reassigns its own `this.props`. ' +
547 + 'Please file an issue.',
548 + getComponentName(finishedWork.type) || 'instance',
549 + );
550 + }
551 + if (instance.state !== finishedWork.memoizedState) {
552 + console.error(
553 + 'Expected %s state to match memoized state before ' +
554 + 'componentDidMount. ' +
555 + 'This might either be because of a bug in React, or because ' +
556 + 'a component reassigns its own `this.state`. ' +
557 + 'Please file an issue.',
558 + getComponentName(finishedWork.type) || 'instance',
559 + );
560 + }
561 + }
562 + }
563 if (
534 - finishedWork.type === finishedWork.elementType &&
535 - !didWarnAboutReassigningProps
564 + enableProfilerTimer &&
565 + enableProfilerCommitHooks &&
566 + finishedWork.mode & ProfileMode
567 ) {
537 - if (instance.props !== finishedWork.memoizedProps) {
538 - console.error(
539 - 'Expected %s props to match memoized props before ' +
540 - 'componentDidMount. ' +
541 - 'This might either be because of a bug in React, or because ' +
542 - 'a component reassigns its own `this.props`. ' +
543 - 'Please file an issue.',
544 - getComponentName(finishedWork.type) || 'instance',
545 - );
568 + try {
569 + startLayoutEffectTimer();
570 + instance.componentDidMount();
571 + } finally {
572 + recordLayoutEffectDuration(finishedWork);
573 }
547 - if (instance.state !== finishedWork.memoizedState) {
548 - console.error(
549 - 'Expected %s state to match memoized state before ' +
550 - 'componentDidMount. ' +
551 - 'This might either be because of a bug in React, or because ' +
552 - 'a component reassigns its own `this.state`. ' +
553 - 'Please file an issue.',
554 - getComponentName(finishedWork.type) || 'instance',
555 - );
556 - }
557 - }
558 - }
559 - if (
560 - enableProfilerTimer &&
561 - enableProfilerCommitHooks &&
562 - finishedWork.mode & ProfileMode
563 - ) {
564 - try {
565 - startLayoutEffectTimer();
574 + } else {
575 instance.componentDidMount();
567 - } finally {
568 - recordLayoutEffectDuration(finishedWork);
576 }
577 } else {
571 - instance.componentDidMount();
578 + const prevProps =
579 + finishedWork.elementType === finishedWork.type
580 + ? current.memoizedProps
581 + : resolveDefaultProps(finishedWork.type, current.memoizedProps);
582 + const prevState = current.memoizedState;
583 + // We could update instance props and state here,
584 + // but instead we rely on them being set during last render.
585 + // TODO: revisit this when we implement resuming.
586 + if (__DEV__) {
587 + if (
588 + finishedWork.type === finishedWork.elementType &&
589 + !didWarnAboutReassigningProps
590 + ) {
591 + if (instance.props !== finishedWork.memoizedProps) {
592 + console.error(
593 + 'Expected %s props to match memoized props before ' +
594 + 'componentDidUpdate. ' +
595 + 'This might either be because of a bug in React, or because ' +
596 + 'a component reassigns its own `this.props`. ' +
597 + 'Please file an issue.',
598 + getComponentName(finishedWork.type) || 'instance',
599 + );
600 + }
601 + if (instance.state !== finishedWork.memoizedState) {
602 + console.error(
603 + 'Expected %s state to match memoized state before ' +
604 + 'componentDidUpdate. ' +
605 + 'This might either be because of a bug in React, or because ' +
606 + 'a component reassigns its own `this.state`. ' +
607 + 'Please file an issue.',
608 + getComponentName(finishedWork.type) || 'instance',
609 + );
610 + }
611 + }
612 + }
613 + if (
614 + enableProfilerTimer &&
615 + enableProfilerCommitHooks &&
616 + finishedWork.mode & ProfileMode
617 + ) {
618 + try {
619 + startLayoutEffectTimer();
620 + instance.componentDidUpdate(
621 + prevProps,
622 + prevState,
623 + instance.__reactInternalSnapshotBeforeUpdate,
624 + );
625 + } finally {
626 + recordLayoutEffectDuration(finishedWork);
627 + }
628 + } else {
629 + instance.componentDidUpdate(
630 + prevProps,
631 + prevState,
632 + instance.__reactInternalSnapshotBeforeUpdate,
633 + );
634 + }
635 }
573 - } else {
574 - const prevProps =
575 - finishedWork.elementType === finishedWork.type
576 - ? current.memoizedProps
577 - : resolveDefaultProps(finishedWork.type, current.memoizedProps);
578 - const prevState = current.memoizedState;
579 - // We could update instance props and state here,
580 - // but instead we rely on them being set during last render.
581 - // TODO: revisit this when we implement resuming.
636 + }
637 +
638 + // TODO: I think this is now always non-null by the time it reaches the
639 + // commit phase. Consider removing the type check.
640 + const updateQueue: UpdateQueue<
641 + *,
642 + > | null = (finishedWork.updateQueue: any);
643 + if (updateQueue !== null) {
644 if (__DEV__) {
645 if (
646 finishedWork.type === finishedWork.elementType &&
@@ -587,7 +649,7 @@ function commitLifeCycles(
649 if (instance.props !== finishedWork.memoizedProps) {
650 console.error(
651 'Expected %s props to match memoized props before ' +
590 - 'componentDidUpdate. ' +
652 + 'processing the update queue. ' +
653 'This might either be because of a bug in React, or because ' +
654 'a component reassigns its own `this.props`. ' +
655 'Please file an issue.',
@@ -597,7 +659,7 @@ function commitLifeCycles(
659 if (instance.state !== finishedWork.memoizedState) {
660 console.error(
661 'Expected %s state to match memoized state before ' +
600 - 'componentDidUpdate. ' +
662 + 'processing the update queue. ' +
663 'This might either be because of a bug in React, or because ' +
664 'a component reassigns its own `this.state`. ' +
665 'Please file an issue.',
@@ -606,210 +668,166 @@ function commitLifeCycles(
668 }
669 }
670 }
609 - if (
610 - enableProfilerTimer &&
611 - enableProfilerCommitHooks &&
612 - finishedWork.mode & ProfileMode
613 - ) {
614 - try {
615 - startLayoutEffectTimer();
616 - instance.componentDidUpdate(
617 - prevProps,
618 - prevState,
619 - instance.__reactInternalSnapshotBeforeUpdate,
620 - );
621 - } finally {
622 - recordLayoutEffectDuration(finishedWork);
623 - }
624 - } else {
625 - instance.componentDidUpdate(
626 - prevProps,
627 - prevState,
628 - instance.__reactInternalSnapshotBeforeUpdate,
629 - );
630 - }
671 + // We could update instance props and state here,
672 + // but instead we rely on them being set during last render.
673 + // TODO: revisit this when we implement resuming.
674 + commitUpdateQueue(finishedWork, updateQueue, instance);
675 }
676 + break;
677 }
633 -
634 - // TODO: I think this is now always non-null by the time it reaches the
635 - // commit phase. Consider removing the type check.
636 - const updateQueue: UpdateQueue<
637 - *,
638 - > | null = (finishedWork.updateQueue: any);
639 - if (updateQueue !== null) {
640 - if (__DEV__) {
641 - if (
642 - finishedWork.type === finishedWork.elementType &&
643 - !didWarnAboutReassigningProps
644 - ) {
645 - if (instance.props !== finishedWork.memoizedProps) {
646 - console.error(
647 - 'Expected %s props to match memoized props before ' +
648 - 'processing the update queue. ' +
649 - 'This might either be because of a bug in React, or because ' +
650 - 'a component reassigns its own `this.props`. ' +
651 - 'Please file an issue.',
652 - getComponentName(finishedWork.type) || 'instance',
653 - );
654 - }
655 - if (instance.state !== finishedWork.memoizedState) {
656 - console.error(
657 - 'Expected %s state to match memoized state before ' +
658 - 'processing the update queue. ' +
659 - 'This might either be because of a bug in React, or because ' +
660 - 'a component reassigns its own `this.state`. ' +
661 - 'Please file an issue.',
662 - getComponentName(finishedWork.type) || 'instance',
663 - );
678 + case HostRoot: {
679 + // TODO: I think this is now always non-null by the time it reaches the
680 + // commit phase. Consider removing the type check.
681 + const updateQueue: UpdateQueue<
682 + *,
683 + > | null = (finishedWork.updateQueue: any);
684 + if (updateQueue !== null) {
685 + let instance = null;
686 + if (finishedWork.child !== null) {
687 + switch (finishedWork.child.tag) {
688 + case HostComponent:
689 + instance = getPublicInstance(finishedWork.child.stateNode);
690 + break;
691 + case ClassComponent:
692 + instance = finishedWork.child.stateNode;
693 + break;
694 }
695 }
696 + commitUpdateQueue(finishedWork, updateQueue, instance);
697 }
667 - // We could update instance props and state here,
668 - // but instead we rely on them being set during last render.
669 - // TODO: revisit this when we implement resuming.
670 - commitUpdateQueue(finishedWork, updateQueue, instance);
698 + break;
699 }
672 - return;
673 - }
674 - case HostRoot: {
675 - // TODO: I think this is now always non-null by the time it reaches the
676 - // commit phase. Consider removing the type check.
677 - const updateQueue: UpdateQueue<
678 - *,
679 - > | null = (finishedWork.updateQueue: any);
680 - if (updateQueue !== null) {
681 - let instance = null;
682 - if (finishedWork.child !== null) {
683 - switch (finishedWork.child.tag) {
684 - case HostComponent:
685 - instance = getPublicInstance(finishedWork.child.stateNode);
686 - break;
687 - case ClassComponent:
688 - instance = finishedWork.child.stateNode;
689 - break;
690 - }
700 + case HostComponent: {
701 + const instance: Instance = finishedWork.stateNode;
702 +
703 + // Renderers may schedule work to be done after host components are mounted
704 + // (eg DOM renderer may schedule auto-focus for inputs and form controls).
705 + // These effects should only be committed when components are first mounted,
706 + // aka when there is no current/alternate.
707 + if (current === null && finishedWork.flags & Update) {
708 + const type = finishedWork.type;
709 + const props = finishedWork.memoizedProps;
710 + commitMount(instance, type, props, finishedWork);
711 }
692 - commitUpdateQueue(finishedWork, updateQueue, instance);
693 - }
694 - return;
695 - }
696 - case HostComponent: {
697 - const instance: Instance = finishedWork.stateNode;
712
699 - // Renderers may schedule work to be done after host components are mounted
700 - // (eg DOM renderer may schedule auto-focus for inputs and form controls).
701 - // These effects should only be committed when components are first mounted,
702 - // aka when there is no current/alternate.
703 - if (current === null && finishedWork.flags & Update) {
704 - const type = finishedWork.type;
705 - const props = finishedWork.memoizedProps;
706 - commitMount(instance, type, props, finishedWork);
713 + break;
714 }
715 + case HostText: {
716 + // We have no life-cycles associated with text.
717 + break;
718 + }
719 + case HostPortal: {
720 + // We have no life-cycles associated with portals.
721 + break;
722 + }
723 + case Profiler: {
724 + if (enableProfilerTimer) {
725 + const {onCommit, onRender} = finishedWork.memoizedProps;
726 + const {effectDuration} = finishedWork.stateNode;
727
709 - return;
710 - }
711 - case HostText: {
712 - // We have no life-cycles associated with text.
713 - return;
714 - }
715 - case HostPortal: {
716 - // We have no life-cycles associated with portals.
717 - return;
718 - }
719 - case Profiler: {
720 - if (enableProfilerTimer) {
721 - const {onCommit, onRender} = finishedWork.memoizedProps;
722 - const {effectDuration} = finishedWork.stateNode;
723 -
724 - const commitTime = getCommitTime();
725 -
726 - let phase = current === null ? 'mount' : 'update';
727 - if (enableProfilerNestedUpdatePhase) {
728 - if (isCurrentUpdateNested()) {
729 - phase = 'nested-update';
730 - }
731 - }
728 + const commitTime = getCommitTime();
729
733 - if (typeof onRender === 'function') {
734 - if (enableSchedulerTracing) {
735 - onRender(
736 - finishedWork.memoizedProps.id,
737 - phase,
738 - finishedWork.actualDuration,
739 - finishedWork.treeBaseDuration,
740 - finishedWork.actualStartTime,
741 - commitTime,
742 - finishedRoot.memoizedInteractions,
743 - );
744 - } else {
745 - onRender(
746 - finishedWork.memoizedProps.id,
747 - phase,
748 - finishedWork.actualDuration,
749 - finishedWork.treeBaseDuration,
750 - finishedWork.actualStartTime,
751 - commitTime,
752 - );
730 + let phase = current === null ? 'mount' : 'update';
731 + if (enableProfilerNestedUpdatePhase) {
732 + if (isCurrentUpdateNested()) {
733 + phase = 'nested-update';
734 + }
735 }
754 - }
736
756 - if (enableProfilerCommitHooks) {
757 - if (typeof onCommit === 'function') {
737 + if (typeof onRender === 'function') {
738 if (enableSchedulerTracing) {
759 - onCommit(
739 + onRender(
740 finishedWork.memoizedProps.id,
741 phase,
762 - effectDuration,
742 + finishedWork.actualDuration,
743 + finishedWork.treeBaseDuration,
744 + finishedWork.actualStartTime,
745 commitTime,
746 finishedRoot.memoizedInteractions,
747 );
748 } else {
767 - onCommit(
749 + onRender(
750 finishedWork.memoizedProps.id,
751 phase,
770 - effectDuration,
752 + finishedWork.actualDuration,
753 + finishedWork.treeBaseDuration,
754 + finishedWork.actualStartTime,
755 commitTime,
756 );
757 }
758 }
759
776 - // Schedule a passive effect for this Profiler to call onPostCommit hooks.
777 - // This effect should be scheduled even if there is no onPostCommit callback for this Profiler,
778 - // because the effect is also where times bubble to parent Profilers.
779 - enqueuePendingPassiveProfilerEffect(finishedWork);
760 + if (enableProfilerCommitHooks) {
761 + if (typeof onCommit === 'function') {
762 + if (enableSchedulerTracing) {
763 + onCommit(
764 + finishedWork.memoizedProps.id,
765 + phase,
766 + effectDuration,
767 + commitTime,
768 + finishedRoot.memoizedInteractions,
769 + );
770 + } else {
771 + onCommit(
772 + finishedWork.memoizedProps.id,
773 + phase,
774 + effectDuration,
775 + commitTime,
776 + );
777 + }
778 + }
779
781 - // Propagate layout effect durations to the next nearest Profiler ancestor.
782 - // Do not reset these values until the next render so DevTools has a chance to read them first.
783 - let parentFiber = finishedWork.return;
784 - while (parentFiber !== null) {
785 - if (parentFiber.tag === Profiler) {
786 - const parentStateNode = parentFiber.stateNode;
787 - parentStateNode.effectDuration += effectDuration;
788 - break;
780 + // Schedule a passive effect for this Profiler to call onPostCommit hooks.
781 + // This effect should be scheduled even if there is no onPostCommit callback for this Profiler,
782 + // because the effect is also where times bubble to parent Profilers.
783 + enqueuePendingPassiveProfilerEffect(finishedWork);
784 +
785 + // Propagate layout effect durations to the next nearest Profiler ancestor.
786 + // Do not reset these values until the next render so DevTools has a chance to read them first.
787 + let parentFiber = finishedWork.return;
788 + while (parentFiber !== null) {
789 + if (parentFiber.tag === Profiler) {
790 + const parentStateNode = parentFiber.stateNode;
791 + parentStateNode.effectDuration += effectDuration;
792 + break;
793 + }
794 + parentFiber = parentFiber.return;
795 }
790 - parentFiber = parentFiber.return;
796 }
797 }
798 + break;
799 }
794 - return;
800 + case SuspenseComponent: {
801 + commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
802 + break;
803 + }
804 + case SuspenseListComponent:
805 + case IncompleteClassComponent:
806 + case FundamentalComponent:
807 + case ScopeComponent:
808 + case OffscreenComponent:
809 + case LegacyHiddenComponent:
810 + break;
811 + default:
812 + invariant(
813 + false,
814 + 'This unit of work tag should not have side-effects. This error is ' +
815 + 'likely caused by a bug in React. Please file an issue.',
816 + );
817 }
796 - case SuspenseComponent: {
797 - commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
798 - return;
818 + }
819 +
820 + if (enableScopeAPI) {
821 + // TODO: This is a temporary solution that allowed us to transition away
822 + // from React Flare on www.
823 + if (finishedWork.flags & Ref && finishedWork.tag !== ScopeComponent) {
824 + commitAttachRef(finishedWork);
825 + }
826 + } else {
827 + if (finishedWork.flags & Ref) {
828 + commitAttachRef(finishedWork);
829 }
800 - case SuspenseListComponent:
801 - case IncompleteClassComponent:
802 - case FundamentalComponent:
803 - case ScopeComponent:
804 - case OffscreenComponent:
805 - case LegacyHiddenComponent:
806 - return;
830 }
808 - invariant(
809 - false,
810 - 'This unit of work tag should not have side-effects. This error is ' +
811 - 'likely caused by a bug in React. Please file an issue.',
812 - );
831 }
832
833 function hideOrUnhideAllChildren(finishedWork, isHidden) {
@@ -1823,12 +1841,88 @@ function commitResetTextContent(current: Fiber) {
1841 resetTextContent(current.stateNode);
1842 }
1843
1844 +export function commitLayoutEffects(
1845 + finishedWork: Fiber,
1846 + root: FiberRoot,
1847 + committedLanes: Lanes,
1848 +): void {
1849 + nextEffect = finishedWork;
1850 + commitLayoutEffects_begin(finishedWork, root, committedLanes);
1851 +}
1852 +
1853 +function commitLayoutEffects_begin(
1854 + subtreeRoot: Fiber,
1855 + root: FiberRoot,
1856 + committedLanes: Lanes,
1857 +) {
1858 + while (nextEffect !== null) {
1859 + const fiber = nextEffect;
1860 + const firstChild = fiber.child;
1861 + if ((fiber.subtreeFlags & LayoutMask) !== NoFlags && firstChild !== null) {
1862 + ensureCorrectReturnPointer(firstChild, fiber);
1863 + nextEffect = firstChild;
1864 + } else {
1865 + commitLayoutMountEffects_complete(subtreeRoot, root, committedLanes);
1866 + }
1867 + }
1868 +}
1869 +
1870 +function commitLayoutMountEffects_complete(
1871 + subtreeRoot: Fiber,
1872 + root: FiberRoot,
1873 + committedLanes: Lanes,
1874 +) {
1875 + while (nextEffect !== null) {
1876 + const fiber = nextEffect;
1877 + if ((fiber.flags & LayoutMask) !== NoFlags) {
1878 + const current = fiber.alternate;
1879 + if (__DEV__) {
1880 + setCurrentDebugFiberInDEV(fiber);
1881 + invokeGuardedCallback(
1882 + null,
1883 + commitLayoutEffectOnFiber,
1884 + null,
1885 + root,
1886 + current,
1887 + fiber,
1888 + committedLanes,
1889 + );
1890 + if (hasCaughtError()) {
1891 + const error = clearCaughtError();
1892 + captureCommitPhaseError(fiber, error);
1893 + }
1894 + resetCurrentDebugFiberInDEV();
1895 + } else {
1896 + try {
1897 + commitLayoutEffectOnFiber(root, current, fiber, committedLanes);
1898 + } catch (error) {
1899 + captureCommitPhaseError(fiber, error);
1900 + }
1901 + }
1902 + }
1903 +
1904 + if (fiber === subtreeRoot) {
1905 + nextEffect = null;
1906 + return;
1907 + }
1908 +
1909 + const sibling = fiber.sibling;
1910 + if (sibling !== null) {
1911 + ensureCorrectReturnPointer(sibling, fiber.return);
1912 + nextEffect = sibling;
1913 + return;
1914 + }
1915 +
1916 + nextEffect = fiber.return;
1917 + }
1918 +}
1919 +
1920 export function commitPassiveMountEffects(
1921 root: FiberRoot,
1828 - firstChild: Fiber,
1922 + finishedWork: Fiber,
1923 ): void {
1830 - nextEffect = firstChild;
1831 - commitPassiveMountEffects_begin(firstChild, root);
1924 + nextEffect = finishedWork;
1925 + commitPassiveMountEffects_begin(finishedWork, root);
1926 }
1927
1928 function commitPassiveMountEffects_begin(subtreeRoot: Fiber, root: FiberRoot) {
@@ -2094,7 +2188,6 @@ export {
2188 commitPlacement,
2189 commitDeletion,
2190 commitWork,
2097 - commitLifeCycles,
2191 commitAttachRef,
2192 commitDetachRef,
2193 };
packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+17 -71
@@ -131,7 +131,6 @@ import {
131 Ref,
132 ContentReset,
133 Snapshot,
134 - Callback,
134 Passive,
135 PassiveStatic,
136 Incomplete,
@@ -190,7 +189,7 @@ import {
189 } from './ReactFiberThrow.new';
190 import {
191 commitBeforeMutationLifeCycles as commitBeforeMutationEffectOnFiber,
193 - commitLifeCycles as commitLayoutEffectOnFiber,
192 + commitLayoutEffects,
193 commitPlacement,
194 commitWork,
195 commitDeletion,
@@ -2073,28 +2072,24 @@ function commitRootImpl(root, renderPriorityLevel) {
2072 // The next phase is the layout phase, where we call effects that read
2073 // the host tree after it's been mutated. The idiomatic use case for this is
2074 // layout, but class component lifecycles also fire here for legacy reasons.
2076 - nextEffect = firstEffect;
2077 - do {
2078 - if (__DEV__) {
2079 - invokeGuardedCallback(null, commitLayoutEffects, null, root, lanes);
2080 - if (hasCaughtError()) {
2081 - invariant(nextEffect !== null, 'Should be working on an effect.');
2082 - const error = clearCaughtError();
2083 - captureCommitPhaseError(nextEffect, error);
2084 - nextEffect = nextEffect.nextEffect;
2085 - }
2086 - } else {
2087 - try {
2088 - commitLayoutEffects(root, lanes);
2089 - } catch (error) {
2090 - invariant(nextEffect !== null, 'Should be working on an effect.');
2091 - captureCommitPhaseError(nextEffect, error);
2092 - nextEffect = nextEffect.nextEffect;
2093 - }
2075 + if (__DEV__) {
2076 + if (enableDebugTracing) {
2077 + logLayoutEffectsStarted(lanes);
2078 }
2095 - } while (nextEffect !== null);
2079 + }
2080 + if (enableSchedulingProfiler) {
2081 + markLayoutEffectsStarted(lanes);
2082 + }
2083 + commitLayoutEffects(finishedWork, root, lanes);
2084 + if (__DEV__) {
2085 + if (enableDebugTracing) {
2086 + logLayoutEffectsStopped();
2087 + }
2088 + }
2089
2097 - nextEffect = null;
2090 + if (enableSchedulingProfiler) {
2091 + markLayoutEffectsStopped();
2092 + }
2093
2094 if (enableProfilerTimer && enableProfilerNestedUpdateScheduledHook) {
2095 rootCommittingMutationOrLayoutEffects = null;
@@ -2420,55 +2415,6 @@ function commitMutationEffects(
2415 }
2416 }
2417
2423 -function commitLayoutEffects(root: FiberRoot, committedLanes: Lanes) {
2424 - if (__DEV__) {
2425 - if (enableDebugTracing) {
2426 - logLayoutEffectsStarted(committedLanes);
2427 - }
2428 - }
2429 -
2430 - if (enableSchedulingProfiler) {
2431 - markLayoutEffectsStarted(committedLanes);
2432 - }
2433 -
2434 - // TODO: Should probably move the bulk of this function to commitWork.
2435 - while (nextEffect !== null) {
2436 - setCurrentDebugFiberInDEV(nextEffect);
2437 -
2438 - const flags = nextEffect.flags;
2439 -
2440 - if (flags & (Update | Callback)) {
2441 - const current = nextEffect.alternate;
2442 - commitLayoutEffectOnFiber(root, current, nextEffect, committedLanes);
2443 - }
2444 -
2445 - if (enableScopeAPI) {
2446 - // TODO: This is a temporary solution that allowed us to transition away
2447 - // from React Flare on www.
2448 - if (flags & Ref && nextEffect.tag !== ScopeComponent) {
2449 - commitAttachRef(nextEffect);
2450 - }
2451 - } else {
2452 - if (flags & Ref) {
2453 - commitAttachRef(nextEffect);
2454 - }
2455 - }
2456 -
2457 - resetCurrentDebugFiberInDEV();
2458 - nextEffect = nextEffect.nextEffect;
2459 - }
2460 -
2461 - if (__DEV__) {
2462 - if (enableDebugTracing) {
2463 - logLayoutEffectsStopped();
2464 - }
2465 - }
2466 -
2467 - if (enableSchedulingProfiler) {
2468 - markLayoutEffectsStopped();
2469 - }
2470 -}
2471 -
2418 export function flushPassiveEffects(): boolean {
2419 // Returns whether passive effects were flushed.
2420 if (pendingPassiveEffectsRenderPriority !== NoSchedulerPriority) {
packages/react/src/__tests__/ReactDOMTracing-test.internal.js
+2 -2
@@ -152,7 +152,7 @@ describe('ReactDOMTracing', () => {
152 onInteractionScheduledWorkCompleted,
153 ).toHaveBeenLastNotifiedOfInteraction(interaction);
154
155 - if (gate(flags => flags.dfsEffectsRefactor)) {
155 + if (gate(flags => flags.enableUseJSStackToTrackPassiveDurations)) {
156 expect(onRender).toHaveBeenCalledTimes(3);
157 } else {
158 // TODO: This is 4 instead of 3 because this update was scheduled at
@@ -310,7 +310,7 @@ describe('ReactDOMTracing', () => {
310 expect(
311 onInteractionScheduledWorkCompleted,
312 ).toHaveBeenLastNotifiedOfInteraction(interaction);
313 - if (gate(flags => flags.dfsEffectsRefactor)) {
313 + if (gate(flags => flags.enableUseJSStackToTrackPassiveDurations)) {
314 expect(onRender).toHaveBeenCalledTimes(3);
315 } else {
316 // TODO: This is 4 instead of 3 because this update was scheduled at
packages/react/src/__tests__/ReactProfiler-test.internal.js
+3 -3
@@ -368,7 +368,7 @@ describe('Profiler', () => {
368
369 renderer.update(<App />);
370
371 - if (gate(flags => flags.dfsEffectsRefactor)) {
371 + if (gate(flags => flags.enableUseJSStackToTrackPassiveDurations)) {
372 // None of the Profiler's subtree was rendered because App bailed out before the Profiler.
373 // So we expect onRender not to be called.
374 expect(callback).not.toHaveBeenCalled();
@@ -4383,7 +4383,7 @@ describe('Profiler', () => {
4383 // because the resolved suspended subtree doesn't contain any passive effects.
4384 // If <AsyncComponentWithCascadingWork> or its decendents had a passive effect,
4385 // onPostCommit would be called again.
4386 - if (gate(flags => flags.dfsEffectsRefactor)) {
4386 + if (gate(flags => flags.enableUseJSStackToTrackPassiveDurations)) {
4387 expect(Scheduler).toFlushAndYield([]);
4388 } else {
4389 expect(Scheduler).toFlushAndYield(['onPostCommit']);
@@ -4874,7 +4874,7 @@ describe('Profiler', () => {
4874 });
4875
4876 if (__DEV__) {
4877 - // @gate dfsEffectsRefactor
4877 + // @gate enableUseJSStackToTrackPassiveDurations
4878 // @gate enableDoubleInvokingEffects
4879 it('double invoking does not disconnect wrapped async work', () => {
4880 ReactFeatureFlags.enableDoubleInvokingEffects = true;
scripts/jest/TestFlags.js
+4 -3
@@ -45,9 +45,10 @@ const environmentFlags = {
45 // Use this for tests that are known to be broken.
46 FIXME: false,
47
48 - // Turn this flag back on (or delete) once the effect list is removed in favor
49 - // of a depth-first traversal using `subtreeTags`.
50 - dfsEffectsRefactor: false,
48 + // Turn these flags back on (or delete) once the effect list is removed in
49 + // favor of a depth-first traversal using `subtreeTags`.
50 + dfsEffectsRefactor: __VARIANT__,
51 + enableUseJSStackToTrackPassiveDurations: false,
52 };
53
54 function getTestFlags() {