* Internal `act`: Unwrapping resolved promises
This update our internal implementation of `act` to support React's new
behavior for unwrapping promises. Like we did with Scheduler, when
something suspends, it will yield to the main thread so the microtasks
can run, then continue in a new task.
I need to implement the same behavior in the public version of `act`,
but there are some additional considerations so I'll do that in a
separate commit.
* Move throwException to after work loop resumes
throwException is the function that finds the nearest boundary and
schedules it for a second render pass. We should only call it right
before we unwind the stack — not if we receive an immediate ping and
render the fiber again.
This was an oversight in 8ef3a7c that I didn't notice because it happens
to mostly work, anyway. What made me notice the mistake is that
throwException also marks the entire render phase as suspended
(RootDidSuspend or RootDidSuspendWithDelay), which is only supposed to
be happen if we show a fallback. One consequence was that, in the
RootDidSuspendWithDelay case, the entire commit phase was blocked,
because that's the exit status we use to block a bad fallback
from appearing.
* Use expando to check whether promise has resolved
Add a `status` expando to a thrown thenable to track when its value has
resolved.
In a later step, we'll also use `value` and `reason` expandos to track
the resolved value.
This is not part of the official JavaScript spec — think of
it as an extension of the Promise API, or a custom interface that is a
superset of Thenable. However, it's inspired by the terminology used
by `Promise.allSettled`.
The intent is that this will be a public API — Suspense implementations
can set these expandos to allow React to unwrap the value synchronously
without waiting a microtask.
* Scaffolding for `experimental_use` hook
Sets up a new experimental hook behind a feature flag, but does not
implement it yet.
* use(promise)
Adds experimental support to Fiber for unwrapping the value of a promise
inside a component. It is not yet implemented for Server Components,
but that is planned.
If promise has already resolved, the value can be unwrapped
"immediately" without showing a fallback. The trick we use to implement
this is to yield to the main thread (literally suspending the work
loop), wait for the microtask queue to drain, then check if the promise
resolved in the meantime. If so, we can resume the last attempted fiber
without unwinding the stack. This functionality was implemented in
previous commits.
Another feature is that the promises do not need to be cached between
attempts. Because we assume idempotent execution of components, React
will track the promises that were used during the previous attempt and
reuse the result. You shouldn't rely on this property, but during
initial render it mostly just works. Updates are trickier, though,
because if you used an uncached promise, we have no way of knowing
whether the underlying data has changed, so we have to unwrap the
promise every time. It will still work, but it's inefficient and can
lead to unnecessary fallbacks if it happens during a discrete update.
When we implement this for Server Components, this will be less of an
issue because there are no updates in that environment. However, it's
still better for performance to cache data requests, so the same
principles largely apply.
The intention is that this will eventually be the only supported way to
suspend on arbitrary promises. Throwing a promise directly will
be deprecated.
218 lines
5.6 KiB
JavaScript
218 lines
5.6 KiB
JavaScript
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow
|
|
*/
|
|
|
|
export type ReactNode =
|
|
| React$Element<any>
|
|
| ReactPortal
|
|
| ReactText
|
|
| ReactFragment
|
|
| ReactProvider<any>
|
|
| ReactConsumer<any>;
|
|
|
|
export type ReactEmpty = null | void | boolean;
|
|
|
|
export type ReactFragment = ReactEmpty | Iterable<React$Node>;
|
|
|
|
export type ReactNodeList = ReactEmpty | React$Node;
|
|
|
|
export type ReactText = string | number;
|
|
|
|
export type ReactProvider<T> = {
|
|
$$typeof: Symbol | number,
|
|
type: ReactProviderType<T>,
|
|
key: null | string,
|
|
ref: null,
|
|
props: {
|
|
value: T,
|
|
children?: ReactNodeList,
|
|
...
|
|
},
|
|
...
|
|
};
|
|
|
|
export type ReactProviderType<T> = {
|
|
$$typeof: Symbol | number,
|
|
_context: ReactContext<T>,
|
|
...
|
|
};
|
|
|
|
export type ReactConsumer<T> = {
|
|
$$typeof: Symbol | number,
|
|
type: ReactContext<T>,
|
|
key: null | string,
|
|
ref: null,
|
|
props: {
|
|
children: (value: T) => ReactNodeList,
|
|
...
|
|
},
|
|
...
|
|
};
|
|
|
|
export type ReactContext<T> = {
|
|
$$typeof: Symbol | number,
|
|
Consumer: ReactContext<T>,
|
|
Provider: ReactProviderType<T>,
|
|
_currentValue: T,
|
|
_currentValue2: T,
|
|
_threadCount: number,
|
|
// DEV only
|
|
_currentRenderer?: Object | null,
|
|
_currentRenderer2?: Object | null,
|
|
// This value may be added by application code
|
|
// to improve DEV tooling display names
|
|
displayName?: string,
|
|
|
|
// only used by ServerContext
|
|
_defaultValue: T,
|
|
_globalName: string,
|
|
...
|
|
};
|
|
|
|
export type ServerContextJSONValue =
|
|
| string
|
|
| boolean
|
|
| number
|
|
| null
|
|
| $ReadOnlyArray<ServerContextJSONValue>
|
|
| {+[key: string]: ServerContextJSONValue};
|
|
|
|
export type ReactServerContext<T: any> = ReactContext<T>;
|
|
|
|
export type ReactPortal = {
|
|
$$typeof: Symbol | number,
|
|
key: null | string,
|
|
containerInfo: any,
|
|
children: ReactNodeList,
|
|
// TODO: figure out the API for cross-renderer implementation.
|
|
implementation: any,
|
|
...
|
|
};
|
|
|
|
export type RefObject = {|
|
|
current: any,
|
|
|};
|
|
|
|
export type ReactScope = {|
|
|
$$typeof: Symbol | number,
|
|
|};
|
|
|
|
export type ReactScopeQuery = (
|
|
type: string,
|
|
props: {[string]: mixed, ...},
|
|
instance: mixed,
|
|
) => boolean;
|
|
|
|
export type ReactScopeInstance = {|
|
|
DO_NOT_USE_queryAllNodes(ReactScopeQuery): null | Array<Object>,
|
|
DO_NOT_USE_queryFirstNode(ReactScopeQuery): null | Object,
|
|
containsNode(Object): boolean,
|
|
getChildContextValues: <T>(context: ReactContext<T>) => Array<T>,
|
|
|};
|
|
|
|
// Mutable source version can be anything (e.g. number, string, immutable data structure)
|
|
// so long as it changes every time any part of the source changes.
|
|
export type MutableSourceVersion = $NonMaybeType<mixed>;
|
|
|
|
export type MutableSourceGetSnapshotFn<
|
|
Source: $NonMaybeType<mixed>,
|
|
Snapshot,
|
|
> = (source: Source) => Snapshot;
|
|
|
|
export type MutableSourceSubscribeFn<Source: $NonMaybeType<mixed>, Snapshot> = (
|
|
source: Source,
|
|
callback: (snapshot: Snapshot) => void,
|
|
) => () => void;
|
|
|
|
export type MutableSourceGetVersionFn = (
|
|
source: $NonMaybeType<mixed>,
|
|
) => MutableSourceVersion;
|
|
|
|
export type MutableSource<Source: $NonMaybeType<mixed>> = {|
|
|
_source: Source,
|
|
|
|
_getVersion: MutableSourceGetVersionFn,
|
|
|
|
// Tracks the version of this source at the time it was most recently read.
|
|
// Used to determine if a source is safe to read from before it has been subscribed to.
|
|
// Version number is only used during mount,
|
|
// since the mechanism for determining safety after subscription is expiration time.
|
|
//
|
|
// As a workaround to support multiple concurrent renderers,
|
|
// we categorize some renderers as primary and others as secondary.
|
|
// We only expect there to be two concurrent renderers at most:
|
|
// React Native (primary) and Fabric (secondary);
|
|
// React DOM (primary) and React ART (secondary).
|
|
// Secondary renderers store their context values on separate fields.
|
|
// We use the same approach for Context.
|
|
_workInProgressVersionPrimary: null | MutableSourceVersion,
|
|
_workInProgressVersionSecondary: null | MutableSourceVersion,
|
|
|
|
// DEV only
|
|
// Used to detect multiple renderers using the same mutable source.
|
|
_currentPrimaryRenderer?: Object | null,
|
|
_currentSecondaryRenderer?: Object | null,
|
|
|
|
// DEV only
|
|
// Used to detect side effects that update a mutable source during render.
|
|
// See https://github.com/facebook/react/issues/19948
|
|
_currentlyRenderingFiber?: Fiber | null,
|
|
_initialVersionAsOfFirstRender?: MutableSourceVersion | null,
|
|
|};
|
|
|
|
// The subset of a Thenable required by things thrown by Suspense.
|
|
// This doesn't require a value to be passed to either handler.
|
|
export interface Wakeable {
|
|
then(onFulfill: () => mixed, onReject: () => mixed): void | Wakeable;
|
|
}
|
|
|
|
// The subset of a Promise that React APIs rely on. This resolves a value.
|
|
// This doesn't require a return value neither from the handler nor the
|
|
// then function.
|
|
interface ThenableImpl<T> {
|
|
then(
|
|
onFulfill: (value: T) => mixed,
|
|
onReject: (error: mixed) => mixed,
|
|
): void | Wakeable;
|
|
}
|
|
interface UntrackedThenable<T> extends ThenableImpl<T> {
|
|
status?: void;
|
|
}
|
|
|
|
export interface PendingThenable<T> extends ThenableImpl<T> {
|
|
status: 'pending';
|
|
}
|
|
|
|
export interface FulfilledThenable<T> extends ThenableImpl<T> {
|
|
status: 'fulfilled';
|
|
value: T;
|
|
}
|
|
|
|
export interface RejectedThenable<T> extends ThenableImpl<T> {
|
|
status: 'rejected';
|
|
reason: mixed;
|
|
}
|
|
|
|
export type Thenable<T> =
|
|
| UntrackedThenable<T>
|
|
| PendingThenable<T>
|
|
| FulfilledThenable<T>
|
|
| RejectedThenable<T>;
|
|
|
|
export type OffscreenMode =
|
|
| 'hidden'
|
|
| 'unstable-defer-without-hiding'
|
|
| 'visible';
|
|
|
|
export type StartTransitionOptions = {
|
|
name?: string,
|
|
};
|
|
|
|
// TODO: Add Context support
|
|
export type Usable<T> = Thenable<T>;
|