Skip to content

Commit

Permalink
Don't fire passive effects during initial mount of a hidden Offscreen…
Browse files Browse the repository at this point in the history
… 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.
  • Loading branch information
acdlite authored Jul 29, 2022
1 parent 2c7dea7 commit 4ea064e
Show file tree
Hide file tree
Showing 10 changed files with 1,250 additions and 322 deletions.
5 changes: 3 additions & 2 deletions packages/react-reconciler/src/ReactFiber.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import {
CacheComponent,
TracingMarkerComponent,
} from './ReactWorkTags';
import {OffscreenVisible} from './ReactFiberOffscreenComponent';
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';

import {isDevToolsPresent} from './ReactFiberDevToolsHook.new';
Expand Down Expand Up @@ -717,7 +718,7 @@ export function createFiberFromOffscreen(
fiber.elementType = REACT_OFFSCREEN_TYPE;
fiber.lanes = lanes;
const primaryChildInstance: OffscreenInstance = {
isHidden: false,
visibility: OffscreenVisible,
pendingMarkers: null,
retryCache: null,
transitions: null,
Expand All @@ -738,7 +739,7 @@ export function createFiberFromLegacyHidden(
// Adding a stateNode for legacy hidden because it's currently using
// the offscreen implementation, which depends on a state node
const instance: OffscreenInstance = {
isHidden: false,
visibility: OffscreenVisible,
pendingMarkers: null,
transitions: null,
retryCache: null,
Expand Down
5 changes: 3 additions & 2 deletions packages/react-reconciler/src/ReactFiber.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import {
CacheComponent,
TracingMarkerComponent,
} from './ReactWorkTags';
import {OffscreenVisible} from './ReactFiberOffscreenComponent';
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';

import {isDevToolsPresent} from './ReactFiberDevToolsHook.old';
Expand Down Expand Up @@ -717,7 +718,7 @@ export function createFiberFromOffscreen(
fiber.elementType = REACT_OFFSCREEN_TYPE;
fiber.lanes = lanes;
const primaryChildInstance: OffscreenInstance = {
isHidden: false,
visibility: OffscreenVisible,
pendingMarkers: null,
retryCache: null,
transitions: null,
Expand All @@ -738,7 +739,7 @@ export function createFiberFromLegacyHidden(
// Adding a stateNode for legacy hidden because it's currently using
// the offscreen implementation, which depends on a state node
const instance: OffscreenInstance = {
isHidden: false,
visibility: OffscreenVisible,
pendingMarkers: null,
transitions: null,
retryCache: null,
Expand Down
Loading

0 comments on commit 4ea064e

Please sign in to comment.