Skip to content

Commit 4ea064e

Browse files
authored
Don't fire passive effects during initial mount of a hidden Offscreen tree (#24967)
* Change OffscreenInstance isHidden to bitmask The isHidden field of OffscreenInstance is a boolean that represents whether the tree is currently hidden. To implement resuable effects, we need to also track whether the passive effects are currently connected. So I've changed this field to a bitmask. No other behavior has changed in this commit. I'll update the effects behavior in the following steps. * Extract passive mount effects to separate functions I'm about to add a "reappear passive effects" function that will share much of the same code as commitPassiveMountEffectOnFiber. To minimize the duplicated code, I've extracted the shared parts into separate functions, similar to what I did for commitLayoutEffectOnFiber and reappearLayoutEffects. This may not save much on code size because Closure will likely inline some of it, anyway, but it makes it harder for the two paths to accidentally diverge. * Don't mount passive effects in a new hidden tree This changes the behavior of Offscreen so that passive effects do not fire when prerendering a brand new tree. Previously, Offscreen did not affect passive effects at all — only layout effects, which mount or unmount whenever the visibility of the tree changes. When hiding an already visible tree, the behavior of passive effects is unchanged, for now; unlike layout effects, the passive effects will not get unmounted. Pre-rendered updates to a hidden tree in this state will also fire normally. This is only temporary, though — the plan is for passive effects to act more like layout effects, and unmount them when the tree is hidden. Perhaps after a delay so that if the visibility toggles quickly back and forth, the effects don't need to remount. I'll implement this separately. * "Atomic" passive commit effects must always fire There are a few cases where commit phase logic always needs to fire even inside a hidden tree. In general, we should try to design algorithms that don't depend on a commit effect running during prerendering, but there's at least one case where I think it makes sense. The experimental Cache component uses reference counting to keep track of the lifetime of a cache instance. This allows us to expose an AbortSignal object that data frameworks can use to cancel aborted requests. These cache objects are considered alive even inside a prerendered tree. To implement this I added an "atomic" passive effect traversal that runs even when a tree is hidden. (As a follow up, we should add a special subtree flag so that we can skip over nodes that don't have them. There are a number of similar subtree flag optimizations that we have planned, so I'll leave them for a later refactor.) The only other feature that currently depends on this behavior is Transition Tracing. I did not add a test for this because Transition Tracing is still in development and doesn't yet work with Offscreen.
1 parent 2c7dea7 commit 4ea064e

10 files changed

+1250
-322
lines changed

packages/react-reconciler/src/ReactFiber.new.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import {
6161
CacheComponent,
6262
TracingMarkerComponent,
6363
} from './ReactWorkTags';
64+
import {OffscreenVisible} from './ReactFiberOffscreenComponent';
6465
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
6566

6667
import {isDevToolsPresent} from './ReactFiberDevToolsHook.new';
@@ -717,7 +718,7 @@ export function createFiberFromOffscreen(
717718
fiber.elementType = REACT_OFFSCREEN_TYPE;
718719
fiber.lanes = lanes;
719720
const primaryChildInstance: OffscreenInstance = {
720-
isHidden: false,
721+
visibility: OffscreenVisible,
721722
pendingMarkers: null,
722723
retryCache: null,
723724
transitions: null,
@@ -738,7 +739,7 @@ export function createFiberFromLegacyHidden(
738739
// Adding a stateNode for legacy hidden because it's currently using
739740
// the offscreen implementation, which depends on a state node
740741
const instance: OffscreenInstance = {
741-
isHidden: false,
742+
visibility: OffscreenVisible,
742743
pendingMarkers: null,
743744
transitions: null,
744745
retryCache: null,

packages/react-reconciler/src/ReactFiber.old.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import {
6161
CacheComponent,
6262
TracingMarkerComponent,
6363
} from './ReactWorkTags';
64+
import {OffscreenVisible} from './ReactFiberOffscreenComponent';
6465
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
6566

6667
import {isDevToolsPresent} from './ReactFiberDevToolsHook.old';
@@ -717,7 +718,7 @@ export function createFiberFromOffscreen(
717718
fiber.elementType = REACT_OFFSCREEN_TYPE;
718719
fiber.lanes = lanes;
719720
const primaryChildInstance: OffscreenInstance = {
720-
isHidden: false,
721+
visibility: OffscreenVisible,
721722
pendingMarkers: null,
722723
retryCache: null,
723724
transitions: null,
@@ -738,7 +739,7 @@ export function createFiberFromLegacyHidden(
738739
// Adding a stateNode for legacy hidden because it's currently using
739740
// the offscreen implementation, which depends on a state node
740741
const instance: OffscreenInstance = {
741-
isHidden: false,
742+
visibility: OffscreenVisible,
742743
pendingMarkers: null,
743744
transitions: null,
744745
retryCache: null,

0 commit comments

Comments
 (0)