@@ -3555,6 +3555,17 @@ export function commitPassiveUnmountEffects(finishedWork: Fiber): void {
3555
3555
3556
3556
function detachAlternateSiblings ( parentFiber : Fiber ) {
3557
3557
if ( deletedTreeCleanUpLevel >= 1 ) {
3558
+ // A fiber was deleted from this parent fiber, but it's still part of the
3559
+ // previous (alternate) parent fiber's list of children. Because children
3560
+ // are a linked list, an earlier sibling that's still alive will be
3561
+ // connected to the deleted fiber via its `alternate`:
3562
+ //
3563
+ // live fiber --alternate--> previous live fiber --sibling--> deleted
3564
+ // fiber
3565
+ //
3566
+ // We can't disconnect `alternate` on nodes that haven't been deleted yet,
3567
+ // but we can disconnect the `sibling` and `child` pointers.
3568
+
3558
3569
const previousFiber = parentFiber . alternate ;
3559
3570
if ( previousFiber !== null ) {
3560
3571
let detachedChild = previousFiber . child ;
@@ -3613,17 +3624,6 @@ function recursivelyTraversePassiveUnmountEffects(parentFiber: Fiber): void {
3613
3624
) ;
3614
3625
}
3615
3626
}
3616
- // A fiber was deleted from this parent fiber, but it's still part of
3617
- // the previous (alternate) parent fiber's list of children. Because
3618
- // children are a linked list, an earlier sibling that's still alive
3619
- // will be connected to the deleted fiber via its `alternate`:
3620
- //
3621
- // live fiber
3622
- // --alternate--> previous live fiber
3623
- // --sibling--> deleted fiber
3624
- //
3625
- // We can't disconnect `alternate` on nodes that haven't been deleted
3626
- // yet, but we can disconnect the `sibling` and `child` pointers.
3627
3627
detachAlternateSiblings ( parentFiber ) ;
3628
3628
}
3629
3629
@@ -3655,18 +3655,103 @@ function commitPassiveUnmountOnFiber(finishedWork: Fiber): void {
3655
3655
}
3656
3656
break ;
3657
3657
}
3658
- // TODO: Disconnect passive effects when a tree is hidden, perhaps after
3659
- // a delay.
3660
- // case OffscreenComponent: {
3661
- // ...
3662
- // }
3658
+ case OffscreenComponent : {
3659
+ const instance : OffscreenInstance = finishedWork . stateNode ;
3660
+ const nextState : OffscreenState | null = finishedWork . memoizedState ;
3661
+
3662
+ const isHidden = nextState !== null ;
3663
+
3664
+ if (
3665
+ isHidden &&
3666
+ instance . visibility & OffscreenPassiveEffectsConnected &&
3667
+ // For backwards compatibility, don't unmount when a tree suspends. In
3668
+ // the future we may change this to unmount after a delay.
3669
+ ( finishedWork . return === null ||
3670
+ finishedWork . return . tag !== SuspenseComponent )
3671
+ ) {
3672
+ // The effects are currently connected. Disconnect them.
3673
+ // TODO: Add option or heuristic to delay before disconnecting the
3674
+ // effects. Then if the tree reappears before the delay has elapsed, we
3675
+ // can skip toggling the effects entirely.
3676
+ instance . visibility &= ~ OffscreenPassiveEffectsConnected ;
3677
+ recursivelyTraverseDisconnectPassiveEffects ( finishedWork ) ;
3678
+ } else {
3679
+ recursivelyTraversePassiveUnmountEffects ( finishedWork ) ;
3680
+ }
3681
+
3682
+ break ;
3683
+ }
3663
3684
default: {
3664
3685
recursivelyTraversePassiveUnmountEffects ( finishedWork ) ;
3665
3686
break ;
3666
3687
}
3667
3688
}
3668
3689
}
3669
3690
3691
+ function recursivelyTraverseDisconnectPassiveEffects ( parentFiber : Fiber ) : void {
3692
+ // Deletions effects can be scheduled on any fiber type. They need to happen
3693
+ // before the children effects have fired.
3694
+ const deletions = parentFiber . deletions ;
3695
+
3696
+ if ( ( parentFiber . flags & ChildDeletion ) !== NoFlags ) {
3697
+ if ( deletions !== null ) {
3698
+ for ( let i = 0 ; i < deletions . length ; i ++ ) {
3699
+ const childToDelete = deletions [ i ] ;
3700
+ // TODO: Convert this to use recursion
3701
+ nextEffect = childToDelete ;
3702
+ commitPassiveUnmountEffectsInsideOfDeletedTree_begin (
3703
+ childToDelete ,
3704
+ parentFiber ,
3705
+ ) ;
3706
+ }
3707
+ }
3708
+ detachAlternateSiblings ( parentFiber ) ;
3709
+ }
3710
+
3711
+ const prevDebugFiber = getCurrentDebugFiberInDEV ( ) ;
3712
+ // TODO: Check PassiveStatic flag
3713
+ let child = parentFiber . child ;
3714
+ while ( child !== null ) {
3715
+ setCurrentDebugFiberInDEV ( child ) ;
3716
+ disconnectPassiveEffect ( child ) ;
3717
+ child = child . sibling ;
3718
+ }
3719
+ setCurrentDebugFiberInDEV ( prevDebugFiber ) ;
3720
+ }
3721
+
3722
+ function disconnectPassiveEffect ( finishedWork : Fiber ) : void {
3723
+ switch ( finishedWork . tag ) {
3724
+ case FunctionComponent:
3725
+ case ForwardRef:
3726
+ case SimpleMemoComponent: {
3727
+ // TODO: Check PassiveStatic flag
3728
+ commitHookPassiveUnmountEffects (
3729
+ finishedWork ,
3730
+ finishedWork . return ,
3731
+ HookPassive ,
3732
+ ) ;
3733
+ // When disconnecting passive effects, we fire the effects in the same
3734
+ // order as during a deletiong: parent before child
3735
+ recursivelyTraverseDisconnectPassiveEffects ( finishedWork ) ;
3736
+ break ;
3737
+ }
3738
+ case OffscreenComponent: {
3739
+ const instance : OffscreenInstance = finishedWork . stateNode ;
3740
+ if ( instance . visibility & OffscreenPassiveEffectsConnected ) {
3741
+ instance . visibility &= ~ OffscreenPassiveEffectsConnected ;
3742
+ recursivelyTraverseDisconnectPassiveEffects ( finishedWork ) ;
3743
+ } else {
3744
+ // The effects are already disconnected.
3745
+ }
3746
+ break ;
3747
+ }
3748
+ default: {
3749
+ recursivelyTraverseDisconnectPassiveEffects ( finishedWork ) ;
3750
+ break ;
3751
+ }
3752
+ }
3753
+ }
3754
+
3670
3755
function commitPassiveUnmountEffectsInsideOfDeletedTree_begin (
3671
3756
deletedSubtreeRoot : Fiber ,
3672
3757
nearestMountedAncestor : Fiber | null ,
0 commit comments