mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
Add Suspensey Images behind a Flag (#32819)
We've known we've wanted this for many years and most of the
implementation was already done for Suspensey CSS. This waits to commit
until images have decoded by default or up to 500ms timeout (same as
suspensey fonts).
It only applies to Transitions, Retries (Suspense), Gesture Transitions
(flag) and Idle (doesn't exist). Sync updates just commit immediately.
`<img loading="lazy" src="..." />` opts out since you explicitly want it
to load lazily in that case.
`<img onLoad={...} src="..." />` also opts out since that implies you're
ok with managing your own reveal.
In the future, we may add an opt in e.g. `<img blocking="render"
src="..." />` that opts into longer timeouts and re-suspends even sync
updates. Perhaps also triggering error boundaries on errors.
The rollout for this would have to go in a major and we may have to
relax the default timeout to not delay too much by default. However, we
can also make this part of `enableViewTransition` so that if you opt-in
by using View Transitions then those animations will suspend on images.
That we could ship in a minor.
This commit is contained in:
parent
540cd65252
commit
efb22d8850
|
|
@ -41,6 +41,12 @@ function Component() {
|
|||
transitions['enter-slide-right'] + ' ' + transitions['exit-slide-left']
|
||||
}>
|
||||
<p className="roboto-font">Slide In from Left, Slide Out to Right</p>
|
||||
<p>
|
||||
<img
|
||||
src="https://react.dev/_next/image?url=%2Fimages%2Fteam%2Fsebmarkbage.jpg&w=3840&q=75"
|
||||
width="300"
|
||||
/>
|
||||
</p>
|
||||
</ViewTransition>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
10
packages/react-art/src/ReactFiberConfigART.js
vendored
10
packages/react-art/src/ReactFiberConfigART.js
vendored
|
|
@ -596,6 +596,14 @@ export function maySuspendCommit(type, props) {
|
|||
return false;
|
||||
}
|
||||
|
||||
export function maySuspendCommitOnUpdate(type, oldProps, newProps) {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function maySuspendCommitInSyncRender(type, props) {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function preloadInstance(type, props) {
|
||||
// Return true to indicate it's already loaded
|
||||
return true;
|
||||
|
|
@ -603,7 +611,7 @@ export function preloadInstance(type, props) {
|
|||
|
||||
export function startSuspendingCommit() {}
|
||||
|
||||
export function suspendInstance(type, props) {}
|
||||
export function suspendInstance(instance, type, props) {}
|
||||
|
||||
export function suspendOnActiveViewTransition(container) {}
|
||||
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ import {
|
|||
disableLegacyMode,
|
||||
enableMoveBefore,
|
||||
disableCommentsAsDOMContainers,
|
||||
enableSuspenseyImages,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
HostComponent,
|
||||
|
|
@ -145,6 +146,10 @@ export type Props = {
|
|||
is?: string,
|
||||
size?: number,
|
||||
multiple?: boolean,
|
||||
src?: string,
|
||||
srcSet?: string,
|
||||
loading?: 'eager' | 'lazy',
|
||||
onLoad?: (event: any) => void,
|
||||
...
|
||||
};
|
||||
type RawProps = {
|
||||
|
|
@ -769,9 +774,9 @@ export function commitMount(
|
|||
// only need to assign one. And Safari just never triggers a new load event which means this technique
|
||||
// is already a noop regardless of which properties are assigned. We should revisit if browsers update
|
||||
// this heuristic in the future.
|
||||
if ((newProps: any).src) {
|
||||
if (newProps.src) {
|
||||
((domElement: any): HTMLImageElement).src = (newProps: any).src;
|
||||
} else if ((newProps: any).srcSet) {
|
||||
} else if (newProps.srcSet) {
|
||||
((domElement: any): HTMLImageElement).srcset = (newProps: any).srcSet;
|
||||
}
|
||||
return;
|
||||
|
|
@ -4974,6 +4979,36 @@ export function isHostHoistableType(
|
|||
}
|
||||
|
||||
export function maySuspendCommit(type: Type, props: Props): boolean {
|
||||
if (!enableSuspenseyImages) {
|
||||
return false;
|
||||
}
|
||||
// Suspensey images are the default, unless you opt-out of with either
|
||||
// loading="lazy" or onLoad={...} which implies you're ok waiting.
|
||||
return (
|
||||
type === 'img' &&
|
||||
props.src != null &&
|
||||
props.src !== '' &&
|
||||
props.onLoad == null &&
|
||||
props.loading !== 'lazy'
|
||||
);
|
||||
}
|
||||
|
||||
export function maySuspendCommitOnUpdate(
|
||||
type: Type,
|
||||
oldProps: Props,
|
||||
newProps: Props,
|
||||
): boolean {
|
||||
return (
|
||||
maySuspendCommit(type, newProps) &&
|
||||
(newProps.src !== oldProps.src || newProps.srcSet !== oldProps.srcSet)
|
||||
);
|
||||
}
|
||||
|
||||
export function maySuspendCommitInSyncRender(
|
||||
type: Type,
|
||||
props: Props,
|
||||
): boolean {
|
||||
// TODO: Allow sync lanes to suspend too with an opt-in.
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -4984,8 +5019,17 @@ export function mayResourceSuspendCommit(resource: Resource): boolean {
|
|||
);
|
||||
}
|
||||
|
||||
export function preloadInstance(type: Type, props: Props): boolean {
|
||||
return true;
|
||||
export function preloadInstance(
|
||||
instance: Instance,
|
||||
type: Type,
|
||||
props: Props,
|
||||
): boolean {
|
||||
// We don't need to preload Suspensey images because the browser will
|
||||
// load them early once we set the src.
|
||||
// If we return true here, we'll still get a suspendInstance call in the
|
||||
// pre-commit phase to determine if we still need to decode the image or
|
||||
// if was dropped from cache. This just avoids rendering Suspense fallback.
|
||||
return !!(instance: any).complete;
|
||||
}
|
||||
|
||||
export function preloadResource(resource: Resource): boolean {
|
||||
|
|
@ -5022,8 +5066,38 @@ export function startSuspendingCommit(): void {
|
|||
};
|
||||
}
|
||||
|
||||
export function suspendInstance(type: Type, props: Props): void {
|
||||
return;
|
||||
const SUSPENSEY_IMAGE_TIMEOUT = 500;
|
||||
|
||||
export function suspendInstance(
|
||||
instance: Instance,
|
||||
type: Type,
|
||||
props: Props,
|
||||
): void {
|
||||
if (!enableSuspenseyImages) {
|
||||
return;
|
||||
}
|
||||
if (suspendedState === null) {
|
||||
throw new Error(
|
||||
'Internal React Error: suspendedState null when it was expected to exists. Please report this as a React bug.',
|
||||
);
|
||||
}
|
||||
const state = suspendedState;
|
||||
if (
|
||||
// $FlowFixMe[prop-missing]
|
||||
typeof instance.decode === 'function' &&
|
||||
typeof setTimeout === 'function'
|
||||
) {
|
||||
// If this browser supports decode() API, we use it to suspend waiting on the image.
|
||||
// The loading should have already started at this point, so it should be enough to
|
||||
// just call decode() which should also wait for the data to finish loading.
|
||||
state.count++;
|
||||
const ping = onUnsuspend.bind(state);
|
||||
Promise.race([
|
||||
// $FlowFixMe[prop-missing]
|
||||
instance.decode(),
|
||||
new Promise(resolve => setTimeout(resolve, SUSPENSEY_IMAGE_TIMEOUT)),
|
||||
]).then(ping, ping);
|
||||
}
|
||||
}
|
||||
|
||||
export function suspendResource(
|
||||
|
|
|
|||
|
|
@ -577,13 +577,36 @@ export function maySuspendCommit(type: Type, props: Props): boolean {
|
|||
return false;
|
||||
}
|
||||
|
||||
export function preloadInstance(type: Type, props: Props): boolean {
|
||||
export function maySuspendCommitOnUpdate(
|
||||
type: Type,
|
||||
oldProps: Props,
|
||||
newProps: Props,
|
||||
): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function maySuspendCommitInSyncRender(
|
||||
type: Type,
|
||||
props: Props,
|
||||
): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function preloadInstance(
|
||||
instance: Instance,
|
||||
type: Type,
|
||||
props: Props,
|
||||
): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
export function startSuspendingCommit(): void {}
|
||||
|
||||
export function suspendInstance(type: Type, props: Props): void {}
|
||||
export function suspendInstance(
|
||||
instance: Instance,
|
||||
type: Type,
|
||||
props: Props,
|
||||
): void {}
|
||||
|
||||
export function suspendOnActiveViewTransition(container: Container): void {}
|
||||
|
||||
|
|
|
|||
|
|
@ -735,14 +735,37 @@ export function maySuspendCommit(type: Type, props: Props): boolean {
|
|||
return false;
|
||||
}
|
||||
|
||||
export function preloadInstance(type: Type, props: Props): boolean {
|
||||
export function maySuspendCommitOnUpdate(
|
||||
type: Type,
|
||||
oldProps: Props,
|
||||
newProps: Props,
|
||||
): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function maySuspendCommitInSyncRender(
|
||||
type: Type,
|
||||
props: Props,
|
||||
): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function preloadInstance(
|
||||
instance: Instance,
|
||||
type: Type,
|
||||
props: Props,
|
||||
): boolean {
|
||||
// Return false to indicate it's already loaded
|
||||
return true;
|
||||
}
|
||||
|
||||
export function startSuspendingCommit(): void {}
|
||||
|
||||
export function suspendInstance(type: Type, props: Props): void {}
|
||||
export function suspendInstance(
|
||||
instance: Instance,
|
||||
type: Type,
|
||||
props: Props,
|
||||
): void {}
|
||||
|
||||
export function suspendOnActiveViewTransition(container: Container): void {}
|
||||
|
||||
|
|
|
|||
|
|
@ -320,7 +320,11 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
|
|||
suspenseyCommitSubscription = null;
|
||||
}
|
||||
|
||||
function suspendInstance(type: string, props: Props): void {
|
||||
function suspendInstance(
|
||||
instance: Instance,
|
||||
type: string,
|
||||
props: Props,
|
||||
): void {
|
||||
const src = props.src;
|
||||
if (type === 'suspensey-thing' && typeof src === 'string') {
|
||||
// Attach a listener to the suspensey thing and create a subscription
|
||||
|
|
@ -624,13 +628,33 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
|
|||
return type === 'suspensey-thing' && typeof props.src === 'string';
|
||||
},
|
||||
|
||||
maySuspendCommitOnUpdate(
|
||||
type: string,
|
||||
oldProps: Props,
|
||||
newProps: Props,
|
||||
): boolean {
|
||||
// Asks whether it's possible for this combination of type and props
|
||||
// to ever need to suspend. This is different from asking whether it's
|
||||
// currently ready because even if it's ready now, it might get purged
|
||||
// from the cache later.
|
||||
return (
|
||||
type === 'suspensey-thing' &&
|
||||
typeof newProps.src === 'string' &&
|
||||
newProps.src !== oldProps.src
|
||||
);
|
||||
},
|
||||
|
||||
maySuspendCommitInSyncRender(type: string, props: Props): boolean {
|
||||
return true;
|
||||
},
|
||||
|
||||
mayResourceSuspendCommit(resource: mixed): boolean {
|
||||
throw new Error(
|
||||
'Resources are not implemented for React Noop yet. This method should not be called',
|
||||
);
|
||||
},
|
||||
|
||||
preloadInstance(type: string, props: Props): boolean {
|
||||
preloadInstance(instance: Instance, type: string, props: Props): boolean {
|
||||
if (type !== 'suspensey-thing' || typeof props.src !== 'string') {
|
||||
throw new Error('Attempted to preload unexpected instance: ' + type);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,10 @@ import type {
|
|||
} from './ReactFiberConfig';
|
||||
import type {Fiber, FiberRoot} from './ReactInternalTypes';
|
||||
import type {Lanes} from './ReactFiberLane';
|
||||
import {includesOnlyViewTransitionEligibleLanes} from './ReactFiberLane';
|
||||
import {
|
||||
includesOnlySuspenseyCommitEligibleLanes,
|
||||
includesOnlyViewTransitionEligibleLanes,
|
||||
} from './ReactFiberLane';
|
||||
import type {SuspenseState, RetryQueue} from './ReactFiberSuspenseComponent';
|
||||
import type {UpdateQueue} from './ReactFiberClassUpdateQueue';
|
||||
import type {FunctionComponentUpdateQueue} from './ReactFiberHooks';
|
||||
|
|
@ -160,6 +163,7 @@ import {
|
|||
mountHoistable,
|
||||
unmountHoistable,
|
||||
prepareToCommitHoistables,
|
||||
maySuspendCommitInSyncRender,
|
||||
suspendInstance,
|
||||
suspendResource,
|
||||
resetFormInstance,
|
||||
|
|
@ -4280,25 +4284,31 @@ export function commitPassiveUnmountEffects(finishedWork: Fiber): void {
|
|||
// ViewTransitions so that we know to also visit those to collect appearing
|
||||
// pairs.
|
||||
let suspenseyCommitFlag = ShouldSuspendCommit;
|
||||
export function accumulateSuspenseyCommit(finishedWork: Fiber): void {
|
||||
export function accumulateSuspenseyCommit(
|
||||
finishedWork: Fiber,
|
||||
committedLanes: Lanes,
|
||||
): void {
|
||||
resetAppearingViewTransitions();
|
||||
accumulateSuspenseyCommitOnFiber(finishedWork);
|
||||
accumulateSuspenseyCommitOnFiber(finishedWork, committedLanes);
|
||||
}
|
||||
|
||||
function recursivelyAccumulateSuspenseyCommit(parentFiber: Fiber): void {
|
||||
function recursivelyAccumulateSuspenseyCommit(
|
||||
parentFiber: Fiber,
|
||||
committedLanes: Lanes,
|
||||
): void {
|
||||
if (parentFiber.subtreeFlags & suspenseyCommitFlag) {
|
||||
let child = parentFiber.child;
|
||||
while (child !== null) {
|
||||
accumulateSuspenseyCommitOnFiber(child);
|
||||
accumulateSuspenseyCommitOnFiber(child, committedLanes);
|
||||
child = child.sibling;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function accumulateSuspenseyCommitOnFiber(fiber: Fiber) {
|
||||
function accumulateSuspenseyCommitOnFiber(fiber: Fiber, committedLanes: Lanes) {
|
||||
switch (fiber.tag) {
|
||||
case HostHoistable: {
|
||||
recursivelyAccumulateSuspenseyCommit(fiber);
|
||||
recursivelyAccumulateSuspenseyCommit(fiber, committedLanes);
|
||||
if (fiber.flags & suspenseyCommitFlag) {
|
||||
if (fiber.memoizedState !== null) {
|
||||
suspendResource(
|
||||
|
|
@ -4308,19 +4318,33 @@ function accumulateSuspenseyCommitOnFiber(fiber: Fiber) {
|
|||
fiber.memoizedProps,
|
||||
);
|
||||
} else {
|
||||
const instance = fiber.stateNode;
|
||||
const type = fiber.type;
|
||||
const props = fiber.memoizedProps;
|
||||
suspendInstance(type, props);
|
||||
// TODO: Allow sync lanes to suspend too with an opt-in.
|
||||
if (
|
||||
includesOnlySuspenseyCommitEligibleLanes(committedLanes) ||
|
||||
maySuspendCommitInSyncRender(type, props)
|
||||
) {
|
||||
suspendInstance(instance, type, props);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case HostComponent: {
|
||||
recursivelyAccumulateSuspenseyCommit(fiber);
|
||||
recursivelyAccumulateSuspenseyCommit(fiber, committedLanes);
|
||||
if (fiber.flags & suspenseyCommitFlag) {
|
||||
const instance = fiber.stateNode;
|
||||
const type = fiber.type;
|
||||
const props = fiber.memoizedProps;
|
||||
suspendInstance(type, props);
|
||||
// TODO: Allow sync lanes to suspend too with an opt-in.
|
||||
if (
|
||||
includesOnlySuspenseyCommitEligibleLanes(committedLanes) ||
|
||||
maySuspendCommitInSyncRender(type, props)
|
||||
) {
|
||||
suspendInstance(instance, type, props);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -4331,10 +4355,10 @@ function accumulateSuspenseyCommitOnFiber(fiber: Fiber) {
|
|||
const container: Container = fiber.stateNode.containerInfo;
|
||||
currentHoistableRoot = getHoistableRoot(container);
|
||||
|
||||
recursivelyAccumulateSuspenseyCommit(fiber);
|
||||
recursivelyAccumulateSuspenseyCommit(fiber, committedLanes);
|
||||
currentHoistableRoot = previousHoistableRoot;
|
||||
} else {
|
||||
recursivelyAccumulateSuspenseyCommit(fiber);
|
||||
recursivelyAccumulateSuspenseyCommit(fiber, committedLanes);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -4352,10 +4376,10 @@ function accumulateSuspenseyCommitOnFiber(fiber: Fiber) {
|
|||
// instances, even if they're in the current tree.
|
||||
const prevFlags = suspenseyCommitFlag;
|
||||
suspenseyCommitFlag = MaySuspendCommit;
|
||||
recursivelyAccumulateSuspenseyCommit(fiber);
|
||||
recursivelyAccumulateSuspenseyCommit(fiber, committedLanes);
|
||||
suspenseyCommitFlag = prevFlags;
|
||||
} else {
|
||||
recursivelyAccumulateSuspenseyCommit(fiber);
|
||||
recursivelyAccumulateSuspenseyCommit(fiber, committedLanes);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
@ -4375,13 +4399,13 @@ function accumulateSuspenseyCommitOnFiber(fiber: Fiber) {
|
|||
trackAppearingViewTransition(name, state);
|
||||
}
|
||||
}
|
||||
recursivelyAccumulateSuspenseyCommit(fiber);
|
||||
recursivelyAccumulateSuspenseyCommit(fiber, committedLanes);
|
||||
break;
|
||||
}
|
||||
// Fallthrough
|
||||
}
|
||||
default: {
|
||||
recursivelyAccumulateSuspenseyCommit(fiber);
|
||||
recursivelyAccumulateSuspenseyCommit(fiber, committedLanes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -116,6 +116,8 @@ import {
|
|||
preparePortalMount,
|
||||
prepareScopeUpdate,
|
||||
maySuspendCommit,
|
||||
maySuspendCommitOnUpdate,
|
||||
maySuspendCommitInSyncRender,
|
||||
mayResourceSuspendCommit,
|
||||
preloadInstance,
|
||||
preloadResource,
|
||||
|
|
@ -167,6 +169,7 @@ import {
|
|||
includesSomeLane,
|
||||
mergeLanes,
|
||||
claimNextRetryLane,
|
||||
includesOnlySuspenseyCommitEligibleLanes,
|
||||
} from './ReactFiberLane';
|
||||
import {resetChildFibers} from './ReactChildFiber';
|
||||
import {createScopeInstance} from './ReactFiberScope';
|
||||
|
|
@ -547,10 +550,16 @@ function updateHostComponent(
|
|||
function preloadInstanceAndSuspendIfNeeded(
|
||||
workInProgress: Fiber,
|
||||
type: Type,
|
||||
props: Props,
|
||||
oldProps: null | Props,
|
||||
newProps: Props,
|
||||
renderLanes: Lanes,
|
||||
) {
|
||||
if (!maySuspendCommit(type, props)) {
|
||||
const maySuspend =
|
||||
oldProps === null
|
||||
? maySuspendCommit(type, newProps)
|
||||
: maySuspendCommitOnUpdate(type, oldProps, newProps);
|
||||
|
||||
if (!maySuspend) {
|
||||
// If this flag was set previously, we can remove it. The flag
|
||||
// represents whether this particular set of props might ever need to
|
||||
// suspend. The safest thing to do is for maySuspendCommit to always
|
||||
|
|
@ -568,15 +577,25 @@ function preloadInstanceAndSuspendIfNeeded(
|
|||
// loaded yet.
|
||||
workInProgress.flags |= MaySuspendCommit;
|
||||
|
||||
// preload the instance if necessary. Even if this is an urgent render there
|
||||
// could be benefits to preloading early.
|
||||
// @TODO we should probably do the preload in begin work
|
||||
const isReady = preloadInstance(type, props);
|
||||
if (!isReady) {
|
||||
if (shouldRemainOnPreviousScreen()) {
|
||||
workInProgress.flags |= ShouldSuspendCommit;
|
||||
if (
|
||||
includesOnlySuspenseyCommitEligibleLanes(renderLanes) ||
|
||||
maySuspendCommitInSyncRender(type, newProps)
|
||||
) {
|
||||
// preload the instance if necessary. Even if this is an urgent render there
|
||||
// could be benefits to preloading early.
|
||||
// @TODO we should probably do the preload in begin work
|
||||
const isReady = preloadInstance(workInProgress.stateNode, type, newProps);
|
||||
if (!isReady) {
|
||||
if (shouldRemainOnPreviousScreen()) {
|
||||
workInProgress.flags |= ShouldSuspendCommit;
|
||||
} else {
|
||||
suspendCommit();
|
||||
}
|
||||
} else {
|
||||
suspendCommit();
|
||||
// Even if we're ready we suspend the commit and check again in the pre-commit
|
||||
// phase if we need to suspend anyway. Such as if it's delayed on decoding or
|
||||
// if it was dropped from the cache while rendering due to pressure.
|
||||
workInProgress.flags |= ShouldSuspendCommit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1104,6 +1123,7 @@ function completeWork(
|
|||
preloadInstanceAndSuspendIfNeeded(
|
||||
workInProgress,
|
||||
type,
|
||||
null,
|
||||
newProps,
|
||||
renderLanes,
|
||||
);
|
||||
|
|
@ -1137,10 +1157,10 @@ function completeWork(
|
|||
return null;
|
||||
}
|
||||
} else {
|
||||
const oldProps = current.memoizedProps;
|
||||
// This is an Instance
|
||||
// We may have props to update on the Hoistable instance.
|
||||
if (supportsMutation) {
|
||||
const oldProps = current.memoizedProps;
|
||||
if (oldProps !== newProps) {
|
||||
markUpdate(workInProgress);
|
||||
}
|
||||
|
|
@ -1160,6 +1180,7 @@ function completeWork(
|
|||
preloadInstanceAndSuspendIfNeeded(
|
||||
workInProgress,
|
||||
type,
|
||||
oldProps,
|
||||
newProps,
|
||||
renderLanes,
|
||||
);
|
||||
|
|
@ -1323,6 +1344,7 @@ function completeWork(
|
|||
preloadInstanceAndSuspendIfNeeded(
|
||||
workInProgress,
|
||||
workInProgress.type,
|
||||
current === null ? null : current.memoizedProps,
|
||||
workInProgress.pendingProps,
|
||||
renderLanes,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -637,6 +637,14 @@ export function includesOnlyViewTransitionEligibleLanes(lanes: Lanes): boolean {
|
|||
return (lanes & (TransitionLanes | RetryLanes | IdleLane)) === lanes;
|
||||
}
|
||||
|
||||
export function includesOnlySuspenseyCommitEligibleLanes(
|
||||
lanes: Lanes,
|
||||
): boolean {
|
||||
return (
|
||||
(lanes & (TransitionLanes | RetryLanes | IdleLane | GestureLane)) === lanes
|
||||
);
|
||||
}
|
||||
|
||||
export function includesBlockingLane(lanes: Lanes): boolean {
|
||||
const SyncDefaultLanes =
|
||||
InputContinuousHydrationLane |
|
||||
|
|
|
|||
|
|
@ -645,9 +645,9 @@ export function logSuspendedCommitPhase(
|
|||
reusableLaneDevToolDetails.color = 'secondary-light';
|
||||
reusableLaneOptions.start = startTime;
|
||||
reusableLaneOptions.end = endTime;
|
||||
// TODO: Make this conditionally "Suspended on Images" or both when we add Suspensey Images.
|
||||
// TODO: Include the exact reason and URLs of what resources suspended.
|
||||
// TODO: This might also be Suspended while waiting on a View Transition.
|
||||
performance.measure('Suspended on CSS', reusableLaneOptions);
|
||||
performance.measure('Suspended on CSS or Images', reusableLaneOptions);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1467,7 +1467,7 @@ function commitRootWhenReady(
|
|||
// transaction, so it track state in its own module scope.
|
||||
// This will also track any newly added or appearing ViewTransition
|
||||
// components for the purposes of forming pairs.
|
||||
accumulateSuspenseyCommit(finishedWork);
|
||||
accumulateSuspenseyCommit(finishedWork, lanes);
|
||||
if (isViewTransitionEligible || isGestureTransition) {
|
||||
// If we're stopping gestures we don't have to wait for any pending
|
||||
// view transition. We'll stop it when we commit.
|
||||
|
|
@ -2638,7 +2638,7 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
|
|||
const props = hostFiber.pendingProps;
|
||||
const isReady = resource
|
||||
? preloadResource(resource)
|
||||
: preloadInstance(type, props);
|
||||
: preloadInstance(hostFiber.stateNode, type, props);
|
||||
if (isReady) {
|
||||
// The data resolved. Resume the work loop as if nothing
|
||||
// suspended. Unlike when a user component suspends, we don't
|
||||
|
|
|
|||
|
|
@ -97,11 +97,17 @@ describe('ReactFiberHostContext', () => {
|
|||
maySuspendCommit(type, props) {
|
||||
return false;
|
||||
},
|
||||
preloadInstance(type, props) {
|
||||
maySuspendCommitOnUpdate(type, oldProps, newProps) {
|
||||
return false;
|
||||
},
|
||||
maySuspendCommitInSyncRender(type, props) {
|
||||
return false;
|
||||
},
|
||||
preloadInstance(instance, type, props) {
|
||||
return true;
|
||||
},
|
||||
startSuspendingCommit() {},
|
||||
suspendInstance(type, props) {},
|
||||
suspendInstance(instance, type, props) {},
|
||||
suspendOnActiveViewTransition(container) {},
|
||||
waitForCommitToBeReady() {
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -88,6 +88,9 @@ export const shouldAttemptEagerTransition =
|
|||
export const detachDeletedInstance = $$$config.detachDeletedInstance;
|
||||
export const requestPostPaintCallback = $$$config.requestPostPaintCallback;
|
||||
export const maySuspendCommit = $$$config.maySuspendCommit;
|
||||
export const maySuspendCommitOnUpdate = $$$config.maySuspendCommitOnUpdate;
|
||||
export const maySuspendCommitInSyncRender =
|
||||
$$$config.maySuspendCommitInSyncRender;
|
||||
export const preloadInstance = $$$config.preloadInstance;
|
||||
export const startSuspendingCommit = $$$config.startSuspendingCommit;
|
||||
export const suspendInstance = $$$config.suspendInstance;
|
||||
|
|
|
|||
|
|
@ -537,14 +537,37 @@ export function maySuspendCommit(type: Type, props: Props): boolean {
|
|||
return false;
|
||||
}
|
||||
|
||||
export function preloadInstance(type: Type, props: Props): boolean {
|
||||
export function maySuspendCommitOnUpdate(
|
||||
type: Type,
|
||||
oldProps: Props,
|
||||
newProps: Props,
|
||||
): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function maySuspendCommitInSyncRender(
|
||||
type: Type,
|
||||
props: Props,
|
||||
): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function preloadInstance(
|
||||
instance: Instance,
|
||||
type: Type,
|
||||
props: Props,
|
||||
): boolean {
|
||||
// Return true to indicate it's already loaded
|
||||
return true;
|
||||
}
|
||||
|
||||
export function startSuspendingCommit(): void {}
|
||||
|
||||
export function suspendInstance(type: Type, props: Props): void {}
|
||||
export function suspendInstance(
|
||||
instance: Instance,
|
||||
type: Type,
|
||||
props: Props,
|
||||
): void {}
|
||||
|
||||
export function suspendOnActiveViewTransition(container: Container): void {}
|
||||
|
||||
|
|
|
|||
|
|
@ -96,6 +96,8 @@ export const enableGestureTransition = __EXPERIMENTAL__;
|
|||
|
||||
export const enableScrollEndPolyfill = __EXPERIMENTAL__;
|
||||
|
||||
export const enableSuspenseyImages = __EXPERIMENTAL__;
|
||||
|
||||
/**
|
||||
* Switches the Fabric API from doing layout in commit work instead of complete work.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ export const enableThrottledScheduling = false;
|
|||
export const enableViewTransition = false;
|
||||
export const enableGestureTransition = false;
|
||||
export const enableScrollEndPolyfill = true;
|
||||
export const enableSuspenseyImages = false;
|
||||
export const enableFragmentRefs = false;
|
||||
export const ownerStackLimit = 1e4;
|
||||
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ export const enableGestureTransition = false;
|
|||
export const enableFastAddPropertiesInDiffing = false;
|
||||
export const enableLazyPublicInstanceInFabric = false;
|
||||
export const enableScrollEndPolyfill = true;
|
||||
export const enableSuspenseyImages = false;
|
||||
export const ownerStackLimit = 1e4;
|
||||
|
||||
export const enableFragmentRefs = false;
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ export const enableGestureTransition = false;
|
|||
export const enableFastAddPropertiesInDiffing = true;
|
||||
export const enableLazyPublicInstanceInFabric = false;
|
||||
export const enableScrollEndPolyfill = true;
|
||||
export const enableSuspenseyImages = false;
|
||||
export const ownerStackLimit = 1e4;
|
||||
|
||||
export const enableFragmentRefs = false;
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ export const enableGestureTransition = false;
|
|||
export const enableFastAddPropertiesInDiffing = false;
|
||||
export const enableLazyPublicInstanceInFabric = false;
|
||||
export const enableScrollEndPolyfill = true;
|
||||
export const enableSuspenseyImages = false;
|
||||
export const enableFragmentRefs = false;
|
||||
export const ownerStackLimit = 1e4;
|
||||
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ export const enableGestureTransition = false;
|
|||
export const enableFastAddPropertiesInDiffing = false;
|
||||
export const enableLazyPublicInstanceInFabric = false;
|
||||
export const enableScrollEndPolyfill = true;
|
||||
export const enableSuspenseyImages = false;
|
||||
|
||||
export const enableFragmentRefs = false;
|
||||
export const ownerStackLimit = 1e4;
|
||||
|
|
|
|||
|
|
@ -113,6 +113,8 @@ export const enableLazyPublicInstanceInFabric = false;
|
|||
|
||||
export const enableGestureTransition = false;
|
||||
|
||||
export const enableSuspenseyImages = false;
|
||||
|
||||
export const ownerStackLimit = 1e4;
|
||||
|
||||
// Flow magic to verify the exports of this file match the original version.
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user