@samitouri / QOS-React / commits / 6daef4e7c8

Make xViewTransitionToHostInstances helpers reusable (#32611)

This prepares from being able to reuse some this in ApplyGesture. These all start with resetting a counter but it's tricky to have to remember to do this and tricky to do from the outside of this module. So we make an exported helper that does the resetting. Ideally it gets inlined. We also stop passing "current" to measureViewTransitionHostInstances. Same thing for cancelViewTransitionHostInstances. This doesn't make sense for "nested" which has not updated and so might not have an alternate. Instead we pass in the old and new name if they might be different.

Sebastian Markbåge committed Mar 14, 2025 at 13:16 UTC 6daef4e7c8eea99c1b7eb7e451e028c6d7358321
2 files changed +110 -67
packages/react-reconciler/src/ReactFiberCommitViewTransitions.js
+103 -60
@@ -71,15 +71,40 @@ export let viewTransitionCancelableChildren: null | Array<
71 Instance | string | Props,
72 > = null; // tupled array where each entry is [instance: Instance, oldName: string, props: Props]
73
74 -export function setViewTransitionCancelableChildren(
75 - children: null | Array<Instance | string | Props>,
74 +export function pushViewTransitionCancelableScope(): null | Array<
75 + Instance | string | Props,
76 +> {
77 + const prevChildren = viewTransitionCancelableChildren;
78 + viewTransitionCancelableChildren = null;
79 + return prevChildren;
80 +}
81 +
82 +export function popViewTransitionCancelableScope(
83 + prevChildren: null | Array<Instance | string | Props>,
84 ): void {
77 - viewTransitionCancelableChildren = children;
85 + viewTransitionCancelableChildren = prevChildren;
86 }
87
88 let viewTransitionHostInstanceIdx = 0;
89
82 -function applyViewTransitionToHostInstances(
90 +export function applyViewTransitionToHostInstances(
91 + child: null | Fiber,
92 + name: string,
93 + className: ?string,
94 + collectMeasurements: null | Array<InstanceMeasurement>,
95 + stopAtNestedViewTransitions: boolean,
96 +): boolean {
97 + viewTransitionHostInstanceIdx = 0;
98 + return applyViewTransitionToHostInstancesRecursive(
99 + child,
100 + name,
101 + className,
102 + collectMeasurements,
103 + stopAtNestedViewTransitions,
104 + );
105 +}
106 +
107 +function applyViewTransitionToHostInstancesRecursive(
108 child: null | Fiber,
109 name: string,
110 className: ?string,
@@ -128,7 +153,7 @@ function applyViewTransitionToHostInstances(
153 // inner most one is the one that handles the update.
154 } else {
155 if (
131 - applyViewTransitionToHostInstances(
156 + applyViewTransitionToHostInstancesRecursive(
157 child.child,
158 name,
159 className,
@@ -207,7 +232,6 @@ function commitAppearingPairViewTransitions(placement: Fiber): void {
232 if (className !== 'none') {
233 // We found a new appearing view transition with the same name as this deletion.
234 // We'll transition between them.
210 - viewTransitionHostInstanceIdx = 0;
235 const inViewport = applyViewTransitionToHostInstances(
236 child.child,
237 name,
@@ -242,7 +266,6 @@ export function commitEnterViewTransitions(placement: Fiber): void {
266 state.paired ? props.share : props.enter,
267 );
268 if (className !== 'none') {
245 - viewTransitionHostInstanceIdx = 0;
269 const inViewport = applyViewTransitionToHostInstances(
270 placement.child,
271 name,
@@ -310,7 +333,6 @@ function commitDeletedPairViewTransitions(deletion: Fiber): void {
333 );
334 if (className !== 'none') {
335 // We found a new appearing view transition with the same name as this deletion.
313 - viewTransitionHostInstanceIdx = 0;
336 const inViewport = applyViewTransitionToHostInstances(
337 child.child,
338 name,
@@ -361,7 +383,6 @@ export function commitExitViewTransitions(deletion: Fiber): void {
383 pair !== undefined ? props.share : props.exit,
384 );
385 if (className !== 'none') {
364 - viewTransitionHostInstanceIdx = 0;
386 const inViewport = applyViewTransitionToHostInstances(
387 deletion.child,
388 name,
@@ -449,7 +470,6 @@ export function commitBeforeUpdateViewTransition(
470 return;
471 }
472 }
452 - viewTransitionHostInstanceIdx = 0;
473 applyViewTransitionToHostInstances(
474 current.child,
475 oldName,
@@ -472,7 +492,6 @@ export function commitNestedViewTransitions(changedParent: Fiber): void {
492 props.layout,
493 );
494 if (className !== 'none') {
475 - viewTransitionHostInstanceIdx = 0;
495 applyViewTransitionToHostInstances(
496 child.child,
497 name,
@@ -553,9 +572,22 @@ export function restoreNestedViewTransitions(changedParent: Fiber): void {
572 }
573 }
574
556 -function cancelViewTransitionHostInstances(
557 - currentViewTransition: Fiber,
575 +export function cancelViewTransitionHostInstances(
576 child: null | Fiber,
577 + oldName: string,
578 + stopAtNestedViewTransitions: boolean,
579 +): void {
580 + viewTransitionHostInstanceIdx = 0;
581 + cancelViewTransitionHostInstancesRecursive(
582 + child,
583 + oldName,
584 + stopAtNestedViewTransitions,
585 + );
586 +}
587 +
588 +function cancelViewTransitionHostInstancesRecursive(
589 + child: null | Fiber,
590 + oldName: string,
591 stopAtNestedViewTransitions: boolean,
592 ): void {
593 if (!supportsMutation) {
@@ -564,10 +596,6 @@ function cancelViewTransitionHostInstances(
596 while (child !== null) {
597 if (child.tag === HostComponent) {
598 const instance: Instance = child.stateNode;
567 - const oldName = getViewTransitionName(
568 - currentViewTransition.memoizedProps,
569 - currentViewTransition.stateNode,
570 - );
599 if (viewTransitionCancelableChildren === null) {
600 viewTransitionCancelableChildren = [];
601 }
@@ -589,9 +617,9 @@ function cancelViewTransitionHostInstances(
617 // Skip any nested view transitions for updates since in that case the
618 // inner most one is the one that handles the update.
619 } else {
592 - cancelViewTransitionHostInstances(
593 - currentViewTransition,
620 + cancelViewTransitionHostInstancesRecursive(
621 child.child,
622 + oldName,
623 stopAtNestedViewTransitions,
624 );
625 }
@@ -599,11 +627,32 @@ function cancelViewTransitionHostInstances(
627 }
628 }
629
602 -function measureViewTransitionHostInstances(
603 - currentViewTransition: Fiber,
630 +export function measureViewTransitionHostInstances(
631 parentViewTransition: Fiber,
632 child: null | Fiber,
606 - name: string,
633 + newName: string,
634 + oldName: string,
635 + className: ?string,
636 + previousMeasurements: null | Array<InstanceMeasurement>,
637 + stopAtNestedViewTransitions: boolean,
638 +): boolean {
639 + viewTransitionHostInstanceIdx = 0;
640 + return measureViewTransitionHostInstancesRecursive(
641 + parentViewTransition,
642 + child,
643 + newName,
644 + oldName,
645 + className,
646 + previousMeasurements,
647 + stopAtNestedViewTransitions,
648 + );
649 +}
650 +
651 +function measureViewTransitionHostInstancesRecursive(
652 + parentViewTransition: Fiber,
653 + child: null | Fiber,
654 + newName: string,
655 + oldName: string,
656 className: ?string,
657 previousMeasurements: null | Array<InstanceMeasurement>,
658 stopAtNestedViewTransitions: boolean,
@@ -654,10 +703,10 @@ function measureViewTransitionHostInstances(
703 applyViewTransitionName(
704 instance,
705 viewTransitionHostInstanceIdx === 0
657 - ? name
706 + ? newName
707 : // If we have multiple Host Instances below, we add a suffix to the name to give
708 // each one a unique name.
660 - name + '_' + viewTransitionHostInstanceIdx,
709 + newName + '_' + viewTransitionHostInstanceIdx,
710 className,
711 );
712 }
@@ -667,10 +716,6 @@ function measureViewTransitionHostInstances(
716 // animating it. However, in the current model this only works if the parent also
717 // doesn't animate. So we have to queue these and wait until we complete the parent
718 // to cancel them.
670 - const oldName = getViewTransitionName(
671 - currentViewTransition.memoizedProps,
672 - currentViewTransition.stateNode,
673 - );
719 if (viewTransitionCancelableChildren === null) {
720 viewTransitionCancelableChildren = [];
721 }
@@ -696,11 +741,11 @@ function measureViewTransitionHostInstances(
741 parentViewTransition.flags |= child.flags & AffectedParentLayout;
742 } else {
743 if (
699 - measureViewTransitionHostInstances(
700 - currentViewTransition,
744 + measureViewTransitionHostInstancesRecursive(
745 parentViewTransition,
746 child.child,
703 - name,
747 + newName,
748 + oldName,
749 className,
750 previousMeasurements,
751 stopAtNestedViewTransitions,
@@ -719,6 +764,11 @@ export function measureUpdateViewTransition(
764 finishedWork: Fiber,
765 ): boolean {
766 const props: ViewTransitionProps = finishedWork.memoizedProps;
767 + const newName = getViewTransitionName(props, finishedWork.stateNode);
768 + const oldName = getViewTransitionName(
769 + current.memoizedProps,
770 + current.stateNode,
771 + );
772 const updateClassName: ?string = getViewTransitionClassName(
773 props.className,
774 props.update,
@@ -745,24 +795,21 @@ export function measureUpdateViewTransition(
795 if (layoutClassName === 'none') {
796 // If we did not update, then all changes are considered a layout. We'll
797 // attempt to cancel.
748 - viewTransitionHostInstanceIdx = 0;
749 - cancelViewTransitionHostInstances(current, finishedWork.child, true);
798 + cancelViewTransitionHostInstances(finishedWork.child, oldName, true);
799 return false;
800 }
801 // We didn't update but we might still apply layout so we measure each
802 // instance to see if it moved or resized.
803 className = layoutClassName;
804 }
756 - const name = getViewTransitionName(props, finishedWork.stateNode);
805 // If nothing changed due to a mutation, or children changing size
806 // and the measurements end up unchanged, we should restore it to not animate.
759 - viewTransitionHostInstanceIdx = 0;
807 const previousMeasurements = current.memoizedState;
808 const inViewport = measureViewTransitionHostInstances(
762 - current,
809 finishedWork,
810 finishedWork.child,
765 - name,
811 + newName,
812 + oldName,
813 className,
814 previousMeasurements,
815 true,
@@ -782,29 +829,25 @@ export function measureNestedViewTransitions(changedParent: Fiber): void {
829 let child = changedParent.child;
830 while (child !== null) {
831 if (child.tag === ViewTransitionComponent) {
785 - const current = child.alternate;
786 - if (current !== null) {
787 - const props: ViewTransitionProps = child.memoizedProps;
788 - const name = getViewTransitionName(props, child.stateNode);
789 - const className: ?string = getViewTransitionClassName(
790 - props.className,
791 - props.layout,
792 - );
793 - viewTransitionHostInstanceIdx = 0;
794 - const inViewport = measureViewTransitionHostInstances(
795 - current,
796 - child,
797 - child.child,
798 - name,
799 - className,
800 - child.memoizedState,
801 - false,
802 - );
803 - if ((child.flags & Update) === NoFlags || !inViewport) {
804 - // Nothing changed.
805 - } else {
806 - scheduleViewTransitionEvent(child, props.onLayout);
807 - }
832 + const props: ViewTransitionProps = child.memoizedProps;
833 + const name = getViewTransitionName(props, child.stateNode);
834 + const className: ?string = getViewTransitionClassName(
835 + props.className,
836 + props.layout,
837 + );
838 + const inViewport = measureViewTransitionHostInstances(
839 + child,
840 + child.child,
841 + name,
842 + name, // Since this is unchanged, new and old name is the same.
843 + className,
844 + child.memoizedState,
845 + false,
846 + );
847 + if ((child.flags & Update) === NoFlags || !inViewport) {
848 + // Nothing changed.
849 + } else {
850 + scheduleViewTransitionEvent(child, props.onLayout);
851 }
852 } else if ((child.subtreeFlags & ViewTransitionStatic) !== NoFlags) {
853 measureNestedViewTransitions(child);
packages/react-reconciler/src/ReactFiberCommitWork.js
+7 -7
@@ -254,7 +254,8 @@ import {
254 resetAppearingViewTransitions,
255 trackAppearingViewTransition,
256 viewTransitionCancelableChildren,
257 - setViewTransitionCancelableChildren,
257 + pushViewTransitionCancelableScope,
258 + popViewTransitionCancelableScope,
259 } from './ReactFiberCommitViewTransitions';
260 import {
261 viewTransitionMutationContext,
@@ -2474,14 +2475,14 @@ function commitAfterMutationEffectsOnFiber(
2475 switch (finishedWork.tag) {
2476 case HostRoot: {
2477 viewTransitionContextChanged = false;
2477 - setViewTransitionCancelableChildren(null);
2478 + pushViewTransitionCancelableScope();
2479 recursivelyTraverseAfterMutationEffects(root, finishedWork, lanes);
2480 if (!viewTransitionContextChanged) {
2481 // If we didn't leak any resizing out to the root, we don't have to transition
2482 // the root itself. This means that we can now safely cancel any cancellations
2483 // that bubbled all the way up.
2484 const cancelableChildren = viewTransitionCancelableChildren;
2484 - setViewTransitionCancelableChildren(null);
2485 + popViewTransitionCancelableScope(null);
2486 if (cancelableChildren !== null) {
2487 for (let i = 0; i < cancelableChildren.length; i += 3) {
2488 cancelViewTransitionName(
@@ -2532,9 +2533,8 @@ function commitAfterMutationEffectsOnFiber(
2533 const wasMutated = (finishedWork.flags & Update) !== NoFlags;
2534
2535 const prevContextChanged = viewTransitionContextChanged;
2535 - const prevCancelableChildren = viewTransitionCancelableChildren;
2536 + const prevCancelableChildren = pushViewTransitionCancelableScope();
2537 viewTransitionContextChanged = false;
2537 - setViewTransitionCancelableChildren(null);
2538 recursivelyTraverseAfterMutationEffects(root, finishedWork, lanes);
2539
2540 if (viewTransitionContextChanged) {
@@ -2557,7 +2557,7 @@ function commitAfterMutationEffectsOnFiber(
2557 prevCancelableChildren,
2558 viewTransitionCancelableChildren,
2559 );
2560 - setViewTransitionCancelableChildren(prevCancelableChildren);
2560 + popViewTransitionCancelableScope(prevCancelableChildren);
2561 }
2562 // TODO: If this doesn't end up canceled, because a parent animates,
2563 // then we should probably issue an event since this instance is part of it.
@@ -2571,7 +2571,7 @@ function commitAfterMutationEffectsOnFiber(
2571 );
2572
2573 // If this boundary did update, we cannot cancel its children so those are dropped.
2574 - setViewTransitionCancelableChildren(prevCancelableChildren);
2574 + popViewTransitionCancelableScope(prevCancelableChildren);
2575 }
2576
2577 if ((finishedWork.flags & AffectedParentLayout) !== NoFlags) {