@@ -3553,6 +3553,60 @@ export function commitPassiveUnmountEffects(finishedWork: Fiber): void {
3553
3553
resetCurrentDebugFiberInDEV ( ) ;
3554
3554
}
3555
3555
3556
+ function detachAlternateSiblings ( parentFiber : Fiber ) {
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
+
3569
+ const previousFiber = parentFiber . alternate ;
3570
+ if ( previousFiber !== null ) {
3571
+ let detachedChild = previousFiber . child ;
3572
+ if ( detachedChild !== null ) {
3573
+ previousFiber . child = null ;
3574
+ do {
3575
+ const detachedSibling = detachedChild . sibling ;
3576
+ detachedChild . sibling = null ;
3577
+ detachedChild = detachedSibling ;
3578
+ } while ( detachedChild !== null ) ;
3579
+ }
3580
+ }
3581
+ }
3582
+ }
3583
+
3584
+ function commitHookPassiveUnmountEffects (
3585
+ finishedWork : Fiber ,
3586
+ nearestMountedAncestor ,
3587
+ hookFlags : HookFlags ,
3588
+ ) {
3589
+ if (
3590
+ enableProfilerTimer &&
3591
+ enableProfilerCommitHooks &&
3592
+ finishedWork . mode & ProfileMode
3593
+ ) {
3594
+ startPassiveEffectTimer ( ) ;
3595
+ commitHookEffectListUnmount (
3596
+ hookFlags ,
3597
+ finishedWork ,
3598
+ nearestMountedAncestor ,
3599
+ ) ;
3600
+ recordPassiveEffectDuration ( finishedWork ) ;
3601
+ } else {
3602
+ commitHookEffectListUnmount (
3603
+ hookFlags ,
3604
+ finishedWork ,
3605
+ nearestMountedAncestor ,
3606
+ ) ;
3607
+ }
3608
+ }
3609
+
3556
3610
function recursivelyTraversePassiveUnmountEffects ( parentFiber : Fiber ) : void {
3557
3611
// Deletions effects can be scheduled on any fiber type. They need to happen
3558
3612
// before the children effects have fired.
@@ -3562,44 +3616,15 @@ function recursivelyTraversePassiveUnmountEffects(parentFiber: Fiber): void {
3562
3616
if ( deletions !== null ) {
3563
3617
for ( let i = 0 ; i < deletions . length ; i ++ ) {
3564
3618
const childToDelete = deletions [ i ] ;
3565
- try {
3566
- // TODO: Convert this to use recursion
3567
- nextEffect = childToDelete ;
3568
- commitPassiveUnmountEffectsInsideOfDeletedTree_begin (
3569
- childToDelete ,
3570
- parentFiber ,
3571
- ) ;
3572
- } catch ( error ) {
3573
- captureCommitPhaseError ( childToDelete , parentFiber , error ) ;
3574
- }
3575
- }
3576
- }
3577
-
3578
- if ( deletedTreeCleanUpLevel >= 1 ) {
3579
- // A fiber was deleted from this parent fiber, but it's still part of
3580
- // the previous (alternate) parent fiber's list of children. Because
3581
- // children are a linked list, an earlier sibling that's still alive
3582
- // will be connected to the deleted fiber via its `alternate`:
3583
- //
3584
- // live fiber
3585
- // --alternate--> previous live fiber
3586
- // --sibling--> deleted fiber
3587
- //
3588
- // We can't disconnect `alternate` on nodes that haven't been deleted
3589
- // yet, but we can disconnect the `sibling` and `child` pointers.
3590
- const previousFiber = parentFiber . alternate ;
3591
- if ( previousFiber !== null ) {
3592
- let detachedChild = previousFiber . child ;
3593
- if ( detachedChild !== null ) {
3594
- previousFiber . child = null ;
3595
- do {
3596
- const detachedSibling = detachedChild . sibling ;
3597
- detachedChild . sibling = null ;
3598
- detachedChild = detachedSibling ;
3599
- } while ( detachedChild !== null ) ;
3600
- }
3619
+ // TODO: Convert this to use recursion
3620
+ nextEffect = childToDelete ;
3621
+ commitPassiveUnmountEffectsInsideOfDeletedTree_begin (
3622
+ childToDelete ,
3623
+ parentFiber ,
3624
+ ) ;
3601
3625
}
3602
3626
}
3627
+ detachAlternateSiblings ( parentFiber ) ;
3603
3628
}
3604
3629
3605
3630
const prevDebugFiber = getCurrentDebugFiberInDEV ( ) ;
@@ -3622,40 +3647,111 @@ function commitPassiveUnmountOnFiber(finishedWork: Fiber): void {
3622
3647
case SimpleMemoComponent : {
3623
3648
recursivelyTraversePassiveUnmountEffects ( finishedWork ) ;
3624
3649
if ( finishedWork . flags & Passive ) {
3625
- if (
3626
- enableProfilerTimer &&
3627
- enableProfilerCommitHooks &&
3628
- finishedWork . mode & ProfileMode
3629
- ) {
3630
- startPassiveEffectTimer ( ) ;
3631
- commitHookEffectListUnmount (
3632
- HookPassive | HookHasEffect ,
3633
- finishedWork ,
3634
- finishedWork . return ,
3635
- ) ;
3636
- recordPassiveEffectDuration ( finishedWork ) ;
3637
- } else {
3638
- commitHookEffectListUnmount (
3639
- HookPassive | HookHasEffect ,
3640
- finishedWork ,
3641
- finishedWork . return ,
3642
- ) ;
3643
- }
3650
+ commitHookPassiveUnmountEffects (
3651
+ finishedWork ,
3652
+ finishedWork . return ,
3653
+ HookPassive | HookHasEffect ,
3654
+ ) ;
3644
3655
}
3645
3656
break ;
3646
3657
}
3647
- // TODO: Disconnect passive effects when a tree is hidden, perhaps after
3648
- // a delay.
3649
- // case OffscreenComponent: {
3650
- // ...
3651
- // }
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
+ }
3652
3684
default: {
3653
3685
recursivelyTraversePassiveUnmountEffects ( finishedWork ) ;
3654
3686
break ;
3655
3687
}
3656
3688
}
3657
3689
}
3658
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
+
3659
3755
function commitPassiveUnmountEffectsInsideOfDeletedTree_begin (
3660
3756
deletedSubtreeRoot : Fiber ,
3661
3757
nearestMountedAncestor : Fiber | null ,
@@ -3728,25 +3824,11 @@ function commitPassiveUnmountInsideDeletedTreeOnFiber(
3728
3824
case FunctionComponent :
3729
3825
case ForwardRef :
3730
3826
case SimpleMemoComponent : {
3731
- if (
3732
- enableProfilerTimer &&
3733
- enableProfilerCommitHooks &&
3734
- current . mode & ProfileMode
3735
- ) {
3736
- startPassiveEffectTimer ( ) ;
3737
- commitHookEffectListUnmount (
3738
- HookPassive ,
3739
- current ,
3740
- nearestMountedAncestor ,
3741
- ) ;
3742
- recordPassiveEffectDuration ( current ) ;
3743
- } else {
3744
- commitHookEffectListUnmount (
3745
- HookPassive ,
3746
- current ,
3747
- nearestMountedAncestor ,
3748
- ) ;
3749
- }
3827
+ commitHookPassiveUnmountEffects (
3828
+ current ,
3829
+ nearestMountedAncestor ,
3830
+ HookPassive ,
3831
+ ) ;
3750
3832
break ;
3751
3833
}
3752
3834
// TODO: run passive unmount effects when unmounting a root.
0 commit comments