mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
Add useSwipeTransition Hook Behind Experimental Flag (#32373)
This Hook will be used to drive a View Transition based on a gesture. ```js const [value, startGesture] = useSwipeTransition(prev, current, next); ``` The `enableSwipeTransition` flag will depend on `enableViewTransition` flag but we may decide to ship them independently. This PR doesn't do anything interesting yet. There will be a lot more PRs to build out the actual functionality. This is just wiring up the plumbing for the new Hook. This first PR is mainly concerned with how the whole starts (and stops). The core API is the `startGesture` function (although there will be other conveniences added in the future). You can call this to start a gesture with a source provider. You can call this multiple times in one event to batch multiple Hooks listening to the same provider. However, each render can only handle one source provider at a time and so it does one render per scheduled gesture provider. This uses a separate `GestureLane` to drive gesture renders by marking the Hook as having an update on that lane. Then schedule a render. These renders should be blocking and in the same microtask as the `startGesture` to ensure it can block the paint. So it's similar to sync. It may not be possible to finish it synchronously e.g. if something suspends. If so, it just tries again later when it can like any other render. This can also happen because it also may not be possible to drive more than one gesture at a time like if we're limited to one View Transition per document. So right now you can only run one gesture at a time in practice. These renders never commit. This means that we can't clear the `GestureLane` the normal way. Instead, we have to clear only the root's `pendingLanes` if we don't have any new renders scheduled. Then wait until something else updates the Fiber after all gestures on it have stopped before it really clears.
This commit is contained in:
parent
32b0cad8f7
commit
a53da6abe1
|
|
@ -6,3 +6,14 @@
|
|||
font-variation-settings:
|
||||
"wdth" 100;
|
||||
}
|
||||
|
||||
.swipe-recognizer {
|
||||
width: 200px;
|
||||
overflow-x: scroll;
|
||||
border: 1px solid #333333;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.swipe-overscroll {
|
||||
width: 200%;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import React, {
|
||||
unstable_ViewTransition as ViewTransition,
|
||||
unstable_Activity as Activity,
|
||||
unstable_useSwipeTransition as useSwipeTransition,
|
||||
useRef,
|
||||
useLayoutEffect,
|
||||
} from 'react';
|
||||
|
||||
import './Page.css';
|
||||
|
|
@ -35,7 +38,8 @@ function Component() {
|
|||
}
|
||||
|
||||
export default function Page({url, navigate}) {
|
||||
const show = url === '/?b';
|
||||
const [renderedUrl, startGesture] = useSwipeTransition('/?a', url, '/?b');
|
||||
const show = renderedUrl === '/?b';
|
||||
function onTransition(viewTransition, types) {
|
||||
const keyframes = [
|
||||
{rotate: '0deg', transformOrigin: '30px 8px'},
|
||||
|
|
@ -44,6 +48,32 @@ export default function Page({url, navigate}) {
|
|||
viewTransition.old.animate(keyframes, 250);
|
||||
viewTransition.new.animate(keyframes, 250);
|
||||
}
|
||||
|
||||
const swipeRecognizer = useRef(null);
|
||||
const activeGesture = useRef(null);
|
||||
function onScroll() {
|
||||
if (activeGesture.current !== null) {
|
||||
return;
|
||||
}
|
||||
// eslint-disable-next-line no-undef
|
||||
const scrollTimeline = new ScrollTimeline({
|
||||
source: swipeRecognizer.current,
|
||||
axis: 'x',
|
||||
});
|
||||
activeGesture.current = startGesture(scrollTimeline);
|
||||
}
|
||||
function onScrollEnd() {
|
||||
if (activeGesture.current !== null) {
|
||||
const cancelGesture = activeGesture.current;
|
||||
activeGesture.current = null;
|
||||
cancelGesture();
|
||||
}
|
||||
}
|
||||
|
||||
useLayoutEffect(() => {
|
||||
swipeRecognizer.current.scrollLeft = show ? 0 : 10000;
|
||||
}, [show]);
|
||||
|
||||
const exclamation = (
|
||||
<ViewTransition name="exclamation" onShare={onTransition}>
|
||||
<span>!</span>
|
||||
|
|
@ -90,6 +120,13 @@ export default function Page({url, navigate}) {
|
|||
<p></p>
|
||||
<p></p>
|
||||
<p></p>
|
||||
<div
|
||||
className="swipe-recognizer"
|
||||
onScroll={onScroll}
|
||||
onScrollEnd={onScrollEnd}
|
||||
ref={swipeRecognizer}>
|
||||
<div className="swipe-overscroll">Swipe me</div>
|
||||
</div>
|
||||
<p></p>
|
||||
<p></p>
|
||||
{show ? null : (
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import type {
|
|||
Usable,
|
||||
Thenable,
|
||||
ReactDebugInfo,
|
||||
StartGesture,
|
||||
} from 'shared/ReactTypes';
|
||||
import type {
|
||||
ContextDependency,
|
||||
|
|
@ -131,6 +132,9 @@ function getPrimitiveStackCache(): Map<string, Array<any>> {
|
|||
if (typeof Dispatcher.useEffectEvent === 'function') {
|
||||
Dispatcher.useEffectEvent((args: empty) => {});
|
||||
}
|
||||
if (typeof Dispatcher.useSwipeTransition === 'function') {
|
||||
Dispatcher.useSwipeTransition(null, null, null);
|
||||
}
|
||||
} finally {
|
||||
readHookLog = hookLog;
|
||||
hookLog = [];
|
||||
|
|
@ -752,31 +756,50 @@ function useEffectEvent<Args, F: (...Array<Args>) => mixed>(callback: F): F {
|
|||
return callback;
|
||||
}
|
||||
|
||||
function useSwipeTransition<T>(
|
||||
previous: T,
|
||||
current: T,
|
||||
next: T,
|
||||
): [T, StartGesture] {
|
||||
nextHook();
|
||||
hookLog.push({
|
||||
displayName: null,
|
||||
primitive: 'SwipeTransition',
|
||||
stackError: new Error(),
|
||||
value: current,
|
||||
debugInfo: null,
|
||||
dispatcherHookName: 'SwipeTransition',
|
||||
});
|
||||
return [current, () => () => {}];
|
||||
}
|
||||
|
||||
const Dispatcher: DispatcherType = {
|
||||
use,
|
||||
readContext,
|
||||
useCacheRefresh,
|
||||
|
||||
use,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useDebugValue,
|
||||
useLayoutEffect,
|
||||
useInsertionEffect,
|
||||
useMemo,
|
||||
useMemoCache,
|
||||
useOptimistic,
|
||||
useReducer,
|
||||
useRef,
|
||||
useState,
|
||||
useDebugValue,
|
||||
useDeferredValue,
|
||||
useTransition,
|
||||
useSyncExternalStore,
|
||||
useDeferredValue,
|
||||
useId,
|
||||
useHostTransitionStatus,
|
||||
useFormState,
|
||||
useActionState,
|
||||
useHostTransitionStatus,
|
||||
useOptimistic,
|
||||
useMemoCache,
|
||||
useCacheRefresh,
|
||||
useEffectEvent,
|
||||
useSwipeTransition,
|
||||
};
|
||||
|
||||
// create a proxy to throw a custom error
|
||||
|
|
|
|||
|
|
@ -24,7 +24,14 @@ import {
|
|||
throwIfInfiniteUpdateLoopDetected,
|
||||
getWorkInProgressRoot,
|
||||
} from './ReactFiberWorkLoop';
|
||||
import {NoLane, NoLanes, mergeLanes, markHiddenUpdate} from './ReactFiberLane';
|
||||
import {
|
||||
NoLane,
|
||||
NoLanes,
|
||||
mergeLanes,
|
||||
markHiddenUpdate,
|
||||
markRootUpdated,
|
||||
GestureLane,
|
||||
} from './ReactFiberLane';
|
||||
import {NoFlags, Placement, Hydrating} from './ReactFiberFlags';
|
||||
import {HostRoot, OffscreenComponent} from './ReactWorkTags';
|
||||
import {OffscreenVisible} from './ReactFiberActivityComponent';
|
||||
|
|
@ -169,6 +176,25 @@ export function enqueueConcurrentRenderForLane(
|
|||
return getRootForUpdatedFiber(fiber);
|
||||
}
|
||||
|
||||
export function enqueueGestureRender(fiber: Fiber): FiberRoot | null {
|
||||
// We can't use the concurrent queuing for these so this is basically just a
|
||||
// short cut for marking the lane on the parent path. It is possible for a
|
||||
// gesture render to suspend and then in the gap get another gesture starting.
|
||||
// However, marking the lane doesn't make much different in this case because
|
||||
// it would have to call startGesture with the same exact provider as was
|
||||
// already rendering. Because otherwise it has no effect on the Hook itself.
|
||||
// TODO: We could potentially solve this case by popping a ScheduledGesture
|
||||
// off the root's queue while we're rendering it so that it can't dedupe
|
||||
// and so new startGesture with the same provider would create a new
|
||||
// ScheduledGesture which goes into a separate render pass anyway.
|
||||
// This is such an edge case it probably doesn't matter much.
|
||||
const root = markUpdateLaneFromFiberToRoot(fiber, null, GestureLane);
|
||||
if (root !== null) {
|
||||
markRootUpdated(root, GestureLane);
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
// Calling this function outside this module should only be done for backwards
|
||||
// compatibility and should always be accompanied by a warning.
|
||||
export function unsafe_markUpdateLaneFromFiberToRoot(
|
||||
|
|
@ -189,7 +215,7 @@ function markUpdateLaneFromFiberToRoot(
|
|||
sourceFiber: Fiber,
|
||||
update: ConcurrentUpdate | null,
|
||||
lane: Lane,
|
||||
): void {
|
||||
): null | FiberRoot {
|
||||
// Update the source fiber's lanes
|
||||
sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane);
|
||||
let alternate = sourceFiber.alternate;
|
||||
|
|
@ -238,10 +264,14 @@ function markUpdateLaneFromFiberToRoot(
|
|||
parent = parent.return;
|
||||
}
|
||||
|
||||
if (isHidden && update !== null && node.tag === HostRoot) {
|
||||
if (node.tag === HostRoot) {
|
||||
const root: FiberRoot = node.stateNode;
|
||||
markHiddenUpdate(root, update, lane);
|
||||
if (isHidden && update !== null) {
|
||||
markHiddenUpdate(root, update, lane);
|
||||
}
|
||||
return root;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getRootForUpdatedFiber(sourceFiber: Fiber): FiberRoot | null {
|
||||
|
|
|
|||
90
packages/react-reconciler/src/ReactFiberGestureScheduler.js
vendored
Normal file
90
packages/react-reconciler/src/ReactFiberGestureScheduler.js
vendored
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {FiberRoot} from './ReactInternalTypes';
|
||||
import type {GestureProvider} from 'shared/ReactTypes';
|
||||
|
||||
import {GestureLane} from './ReactFiberLane';
|
||||
import {ensureRootIsScheduled} from './ReactFiberRootScheduler';
|
||||
|
||||
// This type keeps track of any scheduled or active gestures.
|
||||
export type ScheduledGesture = {
|
||||
provider: GestureProvider,
|
||||
count: number, // The number of times this same provider has been started.
|
||||
prev: null | ScheduledGesture, // The previous scheduled gesture in the queue for this root.
|
||||
next: null | ScheduledGesture, // The next scheduled gesture in the queue for this root.
|
||||
};
|
||||
|
||||
export function scheduleGesture(
|
||||
root: FiberRoot,
|
||||
provider: GestureProvider,
|
||||
): ScheduledGesture {
|
||||
let prev = root.gestures;
|
||||
while (prev !== null) {
|
||||
if (prev.provider === provider) {
|
||||
// Existing instance found.
|
||||
prev.count++;
|
||||
return prev;
|
||||
}
|
||||
const next = prev.next;
|
||||
if (next === null) {
|
||||
break;
|
||||
}
|
||||
prev = next;
|
||||
}
|
||||
// Add new instance to the end of the queue.
|
||||
const gesture: ScheduledGesture = {
|
||||
provider: provider,
|
||||
count: 1,
|
||||
prev: prev,
|
||||
next: null,
|
||||
};
|
||||
if (prev === null) {
|
||||
root.gestures = gesture;
|
||||
} else {
|
||||
prev.next = gesture;
|
||||
}
|
||||
ensureRootIsScheduled(root);
|
||||
return gesture;
|
||||
}
|
||||
|
||||
export function cancelScheduledGesture(
|
||||
root: FiberRoot,
|
||||
gesture: ScheduledGesture,
|
||||
): void {
|
||||
gesture.count--;
|
||||
if (gesture.count === 0) {
|
||||
// Delete the scheduled gesture from the queue.
|
||||
deleteScheduledGesture(root, gesture);
|
||||
}
|
||||
}
|
||||
|
||||
export function deleteScheduledGesture(
|
||||
root: FiberRoot,
|
||||
gesture: ScheduledGesture,
|
||||
): void {
|
||||
if (gesture.prev === null) {
|
||||
if (root.gestures === gesture) {
|
||||
root.gestures = gesture.next;
|
||||
if (root.gestures === null) {
|
||||
// Gestures don't clear their lanes while the gesture is still active but it
|
||||
// might not be scheduled to do any more renders and so we shouldn't schedule
|
||||
// any more gesture lane work until a new gesture is scheduled.
|
||||
root.pendingLanes &= ~GestureLane;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
gesture.prev.next = gesture.next;
|
||||
if (gesture.next !== null) {
|
||||
gesture.next.prev = gesture.prev;
|
||||
}
|
||||
gesture.prev = null;
|
||||
gesture.next = null;
|
||||
}
|
||||
}
|
||||
242
packages/react-reconciler/src/ReactFiberHooks.js
vendored
242
packages/react-reconciler/src/ReactFiberHooks.js
vendored
|
|
@ -14,6 +14,8 @@ import type {
|
|||
Thenable,
|
||||
RejectedThenable,
|
||||
Awaited,
|
||||
StartGesture,
|
||||
GestureProvider,
|
||||
} from 'shared/ReactTypes';
|
||||
import type {
|
||||
Fiber,
|
||||
|
|
@ -26,6 +28,7 @@ import type {Lanes, Lane} from './ReactFiberLane';
|
|||
import type {HookFlags} from './ReactHookEffectTags';
|
||||
import type {Flags} from './ReactFiberFlags';
|
||||
import type {TransitionStatus} from './ReactFiberConfig';
|
||||
import type {ScheduledGesture} from './ReactFiberGestureScheduler';
|
||||
|
||||
import {
|
||||
HostTransitionContext,
|
||||
|
|
@ -42,6 +45,7 @@ import {
|
|||
enableLegacyCache,
|
||||
disableLegacyMode,
|
||||
enableNoCloningMemoCache,
|
||||
enableSwipeTransition,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
REACT_CONTEXT_TYPE,
|
||||
|
|
@ -70,6 +74,8 @@ import {
|
|||
isTransitionLane,
|
||||
markRootEntangled,
|
||||
includesSomeLane,
|
||||
isGestureRender,
|
||||
GestureLane,
|
||||
} from './ReactFiberLane';
|
||||
import {
|
||||
ContinuousEventPriority,
|
||||
|
|
@ -130,6 +136,7 @@ import {
|
|||
enqueueConcurrentHookUpdate,
|
||||
enqueueConcurrentHookUpdateAndEagerlyBailout,
|
||||
enqueueConcurrentRenderForLane,
|
||||
enqueueGestureRender,
|
||||
} from './ReactFiberConcurrentUpdates';
|
||||
import {getTreeId} from './ReactFiberTreeContext';
|
||||
import {now} from './Scheduler';
|
||||
|
|
@ -153,6 +160,11 @@ import {requestCurrentTransition} from './ReactFiberTransition';
|
|||
|
||||
import {callComponentInDEV} from './ReactFiberCallUserSpace';
|
||||
|
||||
import {
|
||||
scheduleGesture,
|
||||
cancelScheduledGesture,
|
||||
} from './ReactFiberGestureScheduler';
|
||||
|
||||
export type Update<S, A> = {
|
||||
lane: Lane,
|
||||
revertLane: Lane,
|
||||
|
|
@ -3960,6 +3972,133 @@ function markUpdateInDevTools<A>(fiber: Fiber, lane: Lane, action: A): void {
|
|||
}
|
||||
}
|
||||
|
||||
type SwipeTransitionGestureUpdate = {
|
||||
gesture: ScheduledGesture,
|
||||
prev: SwipeTransitionGestureUpdate | null,
|
||||
next: SwipeTransitionGestureUpdate | null,
|
||||
};
|
||||
|
||||
type SwipeTransitionUpdateQueue = {
|
||||
pending: null | SwipeTransitionGestureUpdate,
|
||||
dispatch: StartGesture,
|
||||
};
|
||||
|
||||
function startGesture(
|
||||
fiber: Fiber,
|
||||
queue: SwipeTransitionUpdateQueue,
|
||||
gestureProvider: GestureProvider,
|
||||
): () => void {
|
||||
const root = enqueueGestureRender(fiber);
|
||||
if (root === null) {
|
||||
// Already unmounted.
|
||||
// TODO: Should we warn here about starting on an unmounted Fiber?
|
||||
return function cancelGesture() {
|
||||
// Noop.
|
||||
};
|
||||
}
|
||||
const scheduledGesture = scheduleGesture(root, gestureProvider);
|
||||
// Add this particular instance to the queue.
|
||||
// We add multiple of the same provider even if they get batched so
|
||||
// that if we cancel one but not the other we can keep track of this.
|
||||
// Order doesn't matter but we insert in the beginning to avoid two fields.
|
||||
const update: SwipeTransitionGestureUpdate = {
|
||||
gesture: scheduledGesture,
|
||||
prev: null,
|
||||
next: queue.pending,
|
||||
};
|
||||
if (queue.pending !== null) {
|
||||
queue.pending.prev = update;
|
||||
}
|
||||
queue.pending = update;
|
||||
return function cancelGesture(): void {
|
||||
if (update.prev === null) {
|
||||
if (queue.pending === update) {
|
||||
queue.pending = update.next;
|
||||
} else {
|
||||
// This was already cancelled. Avoid double decrementing if someone calls this twice by accident.
|
||||
// TODO: Should we warn here about double cancelling?
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
update.prev.next = update.next;
|
||||
if (update.next !== null) {
|
||||
update.next.prev = update.prev;
|
||||
}
|
||||
update.prev = null;
|
||||
update.next = null;
|
||||
}
|
||||
const cancelledGestured = update.gesture;
|
||||
// Decrement ref count of the root schedule.
|
||||
cancelScheduledGesture(root, cancelledGestured);
|
||||
};
|
||||
}
|
||||
|
||||
function mountSwipeTransition<T>(
|
||||
previous: T,
|
||||
current: T,
|
||||
next: T,
|
||||
): [T, StartGesture] {
|
||||
const queue: SwipeTransitionUpdateQueue = {
|
||||
pending: null,
|
||||
dispatch: (null: any),
|
||||
};
|
||||
const startGestureOnHook: StartGesture = (queue.dispatch = (startGesture.bind(
|
||||
null,
|
||||
currentlyRenderingFiber,
|
||||
queue,
|
||||
): any));
|
||||
const hook = mountWorkInProgressHook();
|
||||
hook.queue = queue;
|
||||
return [current, startGestureOnHook];
|
||||
}
|
||||
|
||||
function updateSwipeTransition<T>(
|
||||
previous: T,
|
||||
current: T,
|
||||
next: T,
|
||||
): [T, StartGesture] {
|
||||
const hook = updateWorkInProgressHook();
|
||||
const queue: SwipeTransitionUpdateQueue = hook.queue;
|
||||
const startGestureOnHook: StartGesture = queue.dispatch;
|
||||
const rootRenderLanes = getWorkInProgressRootRenderLanes();
|
||||
let value = current;
|
||||
if (isGestureRender(rootRenderLanes)) {
|
||||
// We're inside a gesture render. We'll traverse the queue to see if
|
||||
// this specific Hook is part of this gesture and, if so, which
|
||||
// direction to render.
|
||||
const root: FiberRoot | null = getWorkInProgressRoot();
|
||||
if (root === null) {
|
||||
throw new Error(
|
||||
'Expected a work-in-progress root. This is a bug in React. Please file an issue.',
|
||||
);
|
||||
}
|
||||
// We assume that the currently rendering gesture is the one first in the queue.
|
||||
const rootRenderGesture = root.gestures;
|
||||
let update = queue.pending;
|
||||
while (update !== null) {
|
||||
if (rootRenderGesture === update.gesture) {
|
||||
// We had a match, meaning we're currently rendering a direction of this
|
||||
// hook for this gesture.
|
||||
// TODO: Determine which direction this gesture is currently rendering.
|
||||
value = previous;
|
||||
break;
|
||||
}
|
||||
update = update.next;
|
||||
}
|
||||
}
|
||||
if (queue.pending !== null) {
|
||||
// As long as there are any active gestures we need to leave the lane on
|
||||
// in case we need to render it later. Since a gesture render doesn't commit
|
||||
// the only time it really fully gets cleared is if something else rerenders
|
||||
// this component after all the active gestures has cleared.
|
||||
currentlyRenderingFiber.lanes = mergeLanes(
|
||||
currentlyRenderingFiber.lanes,
|
||||
GestureLane,
|
||||
);
|
||||
}
|
||||
return [value, startGestureOnHook];
|
||||
}
|
||||
|
||||
export const ContextOnlyDispatcher: Dispatcher = {
|
||||
readContext,
|
||||
|
||||
|
|
@ -3989,6 +4128,10 @@ export const ContextOnlyDispatcher: Dispatcher = {
|
|||
if (enableUseEffectEventHook) {
|
||||
(ContextOnlyDispatcher: Dispatcher).useEffectEvent = throwInvalidHookError;
|
||||
}
|
||||
if (enableSwipeTransition) {
|
||||
(ContextOnlyDispatcher: Dispatcher).useSwipeTransition =
|
||||
throwInvalidHookError;
|
||||
}
|
||||
|
||||
const HooksDispatcherOnMount: Dispatcher = {
|
||||
readContext,
|
||||
|
|
@ -4019,6 +4162,10 @@ const HooksDispatcherOnMount: Dispatcher = {
|
|||
if (enableUseEffectEventHook) {
|
||||
(HooksDispatcherOnMount: Dispatcher).useEffectEvent = mountEvent;
|
||||
}
|
||||
if (enableSwipeTransition) {
|
||||
(HooksDispatcherOnMount: Dispatcher).useSwipeTransition =
|
||||
mountSwipeTransition;
|
||||
}
|
||||
|
||||
const HooksDispatcherOnUpdate: Dispatcher = {
|
||||
readContext,
|
||||
|
|
@ -4049,6 +4196,10 @@ const HooksDispatcherOnUpdate: Dispatcher = {
|
|||
if (enableUseEffectEventHook) {
|
||||
(HooksDispatcherOnUpdate: Dispatcher).useEffectEvent = updateEvent;
|
||||
}
|
||||
if (enableSwipeTransition) {
|
||||
(HooksDispatcherOnUpdate: Dispatcher).useSwipeTransition =
|
||||
updateSwipeTransition;
|
||||
}
|
||||
|
||||
const HooksDispatcherOnRerender: Dispatcher = {
|
||||
readContext,
|
||||
|
|
@ -4079,6 +4230,10 @@ const HooksDispatcherOnRerender: Dispatcher = {
|
|||
if (enableUseEffectEventHook) {
|
||||
(HooksDispatcherOnRerender: Dispatcher).useEffectEvent = updateEvent;
|
||||
}
|
||||
if (enableSwipeTransition) {
|
||||
(HooksDispatcherOnRerender: Dispatcher).useSwipeTransition =
|
||||
updateSwipeTransition;
|
||||
}
|
||||
|
||||
let HooksDispatcherOnMountInDEV: Dispatcher | null = null;
|
||||
let HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher | null = null;
|
||||
|
|
@ -4296,6 +4451,18 @@ if (__DEV__) {
|
|||
return mountEvent(callback);
|
||||
};
|
||||
}
|
||||
if (enableSwipeTransition) {
|
||||
(HooksDispatcherOnMountInDEV: Dispatcher).useSwipeTransition =
|
||||
function useSwipeTransition<T>(
|
||||
previous: T,
|
||||
current: T,
|
||||
next: T,
|
||||
): [T, StartGesture] {
|
||||
currentHookNameInDev = 'useSwipeTransition';
|
||||
mountHookTypesDev();
|
||||
return mountSwipeTransition(previous, current, next);
|
||||
};
|
||||
}
|
||||
|
||||
HooksDispatcherOnMountWithHookTypesInDEV = {
|
||||
readContext<T>(context: ReactContext<T>): T {
|
||||
|
|
@ -4479,6 +4646,18 @@ if (__DEV__) {
|
|||
return mountEvent(callback);
|
||||
};
|
||||
}
|
||||
if (enableSwipeTransition) {
|
||||
(HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).useSwipeTransition =
|
||||
function useSwipeTransition<T>(
|
||||
previous: T,
|
||||
current: T,
|
||||
next: T,
|
||||
): [T, StartGesture] {
|
||||
currentHookNameInDev = 'useSwipeTransition';
|
||||
updateHookTypesDev();
|
||||
return updateSwipeTransition(previous, current, next);
|
||||
};
|
||||
}
|
||||
|
||||
HooksDispatcherOnUpdateInDEV = {
|
||||
readContext<T>(context: ReactContext<T>): T {
|
||||
|
|
@ -4662,6 +4841,18 @@ if (__DEV__) {
|
|||
return updateEvent(callback);
|
||||
};
|
||||
}
|
||||
if (enableSwipeTransition) {
|
||||
(HooksDispatcherOnUpdateInDEV: Dispatcher).useSwipeTransition =
|
||||
function useSwipeTransition<T>(
|
||||
previous: T,
|
||||
current: T,
|
||||
next: T,
|
||||
): [T, StartGesture] {
|
||||
currentHookNameInDev = 'useSwipeTransition';
|
||||
updateHookTypesDev();
|
||||
return updateSwipeTransition(previous, current, next);
|
||||
};
|
||||
}
|
||||
|
||||
HooksDispatcherOnRerenderInDEV = {
|
||||
readContext<T>(context: ReactContext<T>): T {
|
||||
|
|
@ -4845,6 +5036,18 @@ if (__DEV__) {
|
|||
return updateEvent(callback);
|
||||
};
|
||||
}
|
||||
if (enableSwipeTransition) {
|
||||
(HooksDispatcherOnRerenderInDEV: Dispatcher).useSwipeTransition =
|
||||
function useSwipeTransition<T>(
|
||||
previous: T,
|
||||
current: T,
|
||||
next: T,
|
||||
): [T, StartGesture] {
|
||||
currentHookNameInDev = 'useSwipeTransition';
|
||||
updateHookTypesDev();
|
||||
return updateSwipeTransition(previous, current, next);
|
||||
};
|
||||
}
|
||||
|
||||
InvalidNestedHooksDispatcherOnMountInDEV = {
|
||||
readContext<T>(context: ReactContext<T>): T {
|
||||
|
|
@ -5053,6 +5256,19 @@ if (__DEV__) {
|
|||
return mountEvent(callback);
|
||||
};
|
||||
}
|
||||
if (enableSwipeTransition) {
|
||||
(InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).useSwipeTransition =
|
||||
function useSwipeTransition<T>(
|
||||
previous: T,
|
||||
current: T,
|
||||
next: T,
|
||||
): [T, StartGesture] {
|
||||
currentHookNameInDev = 'useSwipeTransition';
|
||||
warnInvalidHookAccess();
|
||||
mountHookTypesDev();
|
||||
return mountSwipeTransition(previous, current, next);
|
||||
};
|
||||
}
|
||||
|
||||
InvalidNestedHooksDispatcherOnUpdateInDEV = {
|
||||
readContext<T>(context: ReactContext<T>): T {
|
||||
|
|
@ -5261,6 +5477,19 @@ if (__DEV__) {
|
|||
return updateEvent(callback);
|
||||
};
|
||||
}
|
||||
if (enableSwipeTransition) {
|
||||
(InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).useSwipeTransition =
|
||||
function useSwipeTransition<T>(
|
||||
previous: T,
|
||||
current: T,
|
||||
next: T,
|
||||
): [T, StartGesture] {
|
||||
currentHookNameInDev = 'useSwipeTransition';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
return updateSwipeTransition(previous, current, next);
|
||||
};
|
||||
}
|
||||
|
||||
InvalidNestedHooksDispatcherOnRerenderInDEV = {
|
||||
readContext<T>(context: ReactContext<T>): T {
|
||||
|
|
@ -5469,4 +5698,17 @@ if (__DEV__) {
|
|||
return updateEvent(callback);
|
||||
};
|
||||
}
|
||||
if (enableSwipeTransition) {
|
||||
(InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).useSwipeTransition =
|
||||
function useSwipeTransition<T>(
|
||||
previous: T,
|
||||
current: T,
|
||||
next: T,
|
||||
): [T, StartGesture] {
|
||||
currentHookNameInDev = 'useSwipeTransition';
|
||||
warnInvalidHookAccess();
|
||||
updateHookTypesDev();
|
||||
return updateSwipeTransition(previous, current, next);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
52
packages/react-reconciler/src/ReactFiberLane.js
vendored
52
packages/react-reconciler/src/ReactFiberLane.js
vendored
|
|
@ -54,23 +54,24 @@ export const DefaultLane: Lane = /* */ 0b0000000000000000000
|
|||
export const SyncUpdateLanes: Lane =
|
||||
SyncLane | InputContinuousLane | DefaultLane;
|
||||
|
||||
const TransitionHydrationLane: Lane = /* */ 0b0000000000000000000000001000000;
|
||||
const TransitionLanes: Lanes = /* */ 0b0000000001111111111111110000000;
|
||||
const TransitionLane1: Lane = /* */ 0b0000000000000000000000010000000;
|
||||
const TransitionLane2: Lane = /* */ 0b0000000000000000000000100000000;
|
||||
const TransitionLane3: Lane = /* */ 0b0000000000000000000001000000000;
|
||||
const TransitionLane4: Lane = /* */ 0b0000000000000000000010000000000;
|
||||
const TransitionLane5: Lane = /* */ 0b0000000000000000000100000000000;
|
||||
const TransitionLane6: Lane = /* */ 0b0000000000000000001000000000000;
|
||||
const TransitionLane7: Lane = /* */ 0b0000000000000000010000000000000;
|
||||
const TransitionLane8: Lane = /* */ 0b0000000000000000100000000000000;
|
||||
const TransitionLane9: Lane = /* */ 0b0000000000000001000000000000000;
|
||||
const TransitionLane10: Lane = /* */ 0b0000000000000010000000000000000;
|
||||
const TransitionLane11: Lane = /* */ 0b0000000000000100000000000000000;
|
||||
const TransitionLane12: Lane = /* */ 0b0000000000001000000000000000000;
|
||||
const TransitionLane13: Lane = /* */ 0b0000000000010000000000000000000;
|
||||
const TransitionLane14: Lane = /* */ 0b0000000000100000000000000000000;
|
||||
const TransitionLane15: Lane = /* */ 0b0000000001000000000000000000000;
|
||||
export const GestureLane: Lane = /* */ 0b0000000000000000000000001000000;
|
||||
|
||||
const TransitionHydrationLane: Lane = /* */ 0b0000000000000000000000010000000;
|
||||
const TransitionLanes: Lanes = /* */ 0b0000000001111111111111100000000;
|
||||
const TransitionLane1: Lane = /* */ 0b0000000000000000000000100000000;
|
||||
const TransitionLane2: Lane = /* */ 0b0000000000000000000001000000000;
|
||||
const TransitionLane3: Lane = /* */ 0b0000000000000000000010000000000;
|
||||
const TransitionLane4: Lane = /* */ 0b0000000000000000000100000000000;
|
||||
const TransitionLane5: Lane = /* */ 0b0000000000000000001000000000000;
|
||||
const TransitionLane6: Lane = /* */ 0b0000000000000000010000000000000;
|
||||
const TransitionLane7: Lane = /* */ 0b0000000000000000100000000000000;
|
||||
const TransitionLane8: Lane = /* */ 0b0000000000000001000000000000000;
|
||||
const TransitionLane9: Lane = /* */ 0b0000000000000010000000000000000;
|
||||
const TransitionLane10: Lane = /* */ 0b0000000000000100000000000000000;
|
||||
const TransitionLane11: Lane = /* */ 0b0000000000001000000000000000000;
|
||||
const TransitionLane12: Lane = /* */ 0b0000000000010000000000000000000;
|
||||
const TransitionLane13: Lane = /* */ 0b0000000000100000000000000000000;
|
||||
const TransitionLane14: Lane = /* */ 0b0000000001000000000000000000000;
|
||||
|
||||
const RetryLanes: Lanes = /* */ 0b0000011110000000000000000000000;
|
||||
const RetryLane1: Lane = /* */ 0b0000000010000000000000000000000;
|
||||
|
|
@ -175,6 +176,8 @@ function getHighestPriorityLanes(lanes: Lanes | Lane): Lanes {
|
|||
return DefaultHydrationLane;
|
||||
case DefaultLane:
|
||||
return DefaultLane;
|
||||
case GestureLane:
|
||||
return GestureLane;
|
||||
case TransitionHydrationLane:
|
||||
return TransitionHydrationLane;
|
||||
case TransitionLane1:
|
||||
|
|
@ -191,7 +194,6 @@ function getHighestPriorityLanes(lanes: Lanes | Lane): Lanes {
|
|||
case TransitionLane12:
|
||||
case TransitionLane13:
|
||||
case TransitionLane14:
|
||||
case TransitionLane15:
|
||||
return lanes & TransitionLanes;
|
||||
case RetryLane1:
|
||||
case RetryLane2:
|
||||
|
|
@ -459,6 +461,7 @@ function computeExpirationTime(lane: Lane, currentTime: number) {
|
|||
case SyncLane:
|
||||
case InputContinuousHydrationLane:
|
||||
case InputContinuousLane:
|
||||
case GestureLane:
|
||||
// User interactions should expire slightly more quickly.
|
||||
//
|
||||
// NOTE: This is set to the corresponding constant as in Scheduler.js.
|
||||
|
|
@ -486,7 +489,6 @@ function computeExpirationTime(lane: Lane, currentTime: number) {
|
|||
case TransitionLane12:
|
||||
case TransitionLane13:
|
||||
case TransitionLane14:
|
||||
case TransitionLane15:
|
||||
return currentTime + transitionLaneExpirationMs;
|
||||
case RetryLane1:
|
||||
case RetryLane2:
|
||||
|
|
@ -640,7 +642,8 @@ export function includesBlockingLane(lanes: Lanes): boolean {
|
|||
InputContinuousHydrationLane |
|
||||
InputContinuousLane |
|
||||
DefaultHydrationLane |
|
||||
DefaultLane;
|
||||
DefaultLane |
|
||||
GestureLane;
|
||||
return (lanes & SyncDefaultLanes) !== NoLanes;
|
||||
}
|
||||
|
||||
|
|
@ -663,6 +666,11 @@ export function isTransitionLane(lane: Lane): boolean {
|
|||
return (lane & TransitionLanes) !== NoLanes;
|
||||
}
|
||||
|
||||
export function isGestureRender(lanes: Lanes): boolean {
|
||||
// This should render only the one lane.
|
||||
return lanes === GestureLane;
|
||||
}
|
||||
|
||||
export function claimNextTransitionLane(): Lane {
|
||||
// Cycle through the lanes, assigning each new transition to the next lane.
|
||||
// In most cases, this means every transition gets its own lane, until we
|
||||
|
|
@ -1053,7 +1061,6 @@ export function getBumpedLaneForHydrationByLane(lane: Lane): Lane {
|
|||
case TransitionLane12:
|
||||
case TransitionLane13:
|
||||
case TransitionLane14:
|
||||
case TransitionLane15:
|
||||
case RetryLane1:
|
||||
case RetryLane2:
|
||||
case RetryLane3:
|
||||
|
|
@ -1197,7 +1204,8 @@ export function getGroupNameOfHighestPriorityLane(lanes: Lanes): string {
|
|||
InputContinuousHydrationLane |
|
||||
InputContinuousLane |
|
||||
DefaultHydrationLane |
|
||||
DefaultLane)
|
||||
DefaultLane |
|
||||
GestureLane)
|
||||
) {
|
||||
return 'Blocking';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import {
|
|||
enableUpdaterTracking,
|
||||
enableTransitionTracing,
|
||||
disableLegacyMode,
|
||||
enableSwipeTransition,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {initializeUpdateQueue} from './ReactFiberClassUpdateQueue';
|
||||
import {LegacyRoot, ConcurrentRoot} from './ReactRootTags';
|
||||
|
|
@ -97,6 +98,10 @@ function FiberRootNode(
|
|||
|
||||
this.formState = formState;
|
||||
|
||||
if (enableSwipeTransition) {
|
||||
this.gestures = null;
|
||||
}
|
||||
|
||||
this.incompleteTransitions = new Map();
|
||||
if (enableTransitionTracing) {
|
||||
this.transitionCallbacks = null;
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import {
|
|||
enableComponentPerformanceTrack,
|
||||
enableSiblingPrerendering,
|
||||
enableYieldingBeforePassive,
|
||||
enableSwipeTransition,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
NoLane,
|
||||
|
|
@ -32,6 +33,7 @@ import {
|
|||
claimNextTransitionLane,
|
||||
getNextLanesToFlushSync,
|
||||
checkIfRootIsPrerendering,
|
||||
isGestureRender,
|
||||
} from './ReactFiberLane';
|
||||
import {
|
||||
CommitContext,
|
||||
|
|
@ -211,7 +213,8 @@ function flushSyncWorkAcrossRoots_impl(
|
|||
rootHasPendingCommit,
|
||||
);
|
||||
if (
|
||||
includesSyncLane(nextLanes) &&
|
||||
(includesSyncLane(nextLanes) ||
|
||||
(enableSwipeTransition && isGestureRender(nextLanes))) &&
|
||||
!checkIfRootIsPrerendering(root, nextLanes)
|
||||
) {
|
||||
// This root has pending sync work. Flush it now.
|
||||
|
|
@ -296,7 +299,8 @@ function processRootScheduleInMicrotask() {
|
|||
syncTransitionLanes !== NoLanes ||
|
||||
// Common case: we're not treating any extra lanes as synchronous, so we
|
||||
// can just check if the next lanes are sync.
|
||||
includesSyncLane(nextLanes)
|
||||
includesSyncLane(nextLanes) ||
|
||||
(enableSwipeTransition && isGestureRender(nextLanes))
|
||||
) {
|
||||
mightHavePendingSyncWork = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ import {
|
|||
enableYieldingBeforePassive,
|
||||
enableThrottledScheduling,
|
||||
enableViewTransition,
|
||||
enableSwipeTransition,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||
import is from 'shared/objectIs';
|
||||
|
|
@ -184,6 +185,8 @@ import {
|
|||
claimNextTransitionLane,
|
||||
checkIfRootIsPrerendering,
|
||||
includesOnlyViewTransitionEligibleLanes,
|
||||
isGestureRender,
|
||||
GestureLane,
|
||||
} from './ReactFiberLane';
|
||||
import {
|
||||
DiscreteEventPriority,
|
||||
|
|
@ -338,6 +341,7 @@ import {
|
|||
import {getMaskedContext, getUnmaskedContext} from './ReactFiberContext';
|
||||
import {peekEntangledActionLane} from './ReactFiberAsyncAction';
|
||||
import {logUncaughtError} from './ReactFiberErrorLogger';
|
||||
import {deleteScheduledGesture} from './ReactFiberGestureScheduler';
|
||||
|
||||
const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
|
||||
|
||||
|
|
@ -3287,6 +3291,13 @@ function commitRoot(
|
|||
const concurrentlyUpdatedLanes = getConcurrentlyUpdatedLanes();
|
||||
remainingLanes = mergeLanes(remainingLanes, concurrentlyUpdatedLanes);
|
||||
|
||||
if (enableSwipeTransition && root.gestures === null) {
|
||||
// Gestures don't clear their lanes while the gesture is still active but it
|
||||
// might not be scheduled to do any more renders and so we shouldn't schedule
|
||||
// any more gesture lane work until a new gesture is scheduled.
|
||||
remainingLanes &= ~GestureLane;
|
||||
}
|
||||
|
||||
markRootFinished(
|
||||
root,
|
||||
lanes,
|
||||
|
|
@ -3310,6 +3321,21 @@ function commitRoot(
|
|||
// times out.
|
||||
}
|
||||
|
||||
if (enableSwipeTransition && isGestureRender(lanes)) {
|
||||
// This is a special kind of render that doesn't commit regular effects.
|
||||
commitGestureOnRoot(
|
||||
root,
|
||||
finishedWork,
|
||||
recoverableErrors,
|
||||
enableProfilerTimer
|
||||
? suspendedCommitReason === IMMEDIATE_COMMIT
|
||||
? completedRenderEndTime
|
||||
: commitStartTime
|
||||
: 0,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// workInProgressX might be overwritten, so we want
|
||||
// to store it in pendingPassiveX until they get processed
|
||||
// We need to pass this through as an argument to commitRoot
|
||||
|
|
@ -3802,6 +3828,24 @@ function flushSpawnedWork(): void {
|
|||
}
|
||||
}
|
||||
|
||||
function commitGestureOnRoot(
|
||||
root: FiberRoot,
|
||||
finishedWork: null | Fiber,
|
||||
recoverableErrors: null | Array<CapturedValue<mixed>>,
|
||||
renderEndTime: number, // Profiling-only
|
||||
): void {
|
||||
// We assume that the gesture we just rendered was the first one in the queue.
|
||||
const finishedGesture = root.gestures;
|
||||
if (finishedGesture === null) {
|
||||
throw new Error(
|
||||
'Finished rendering the gesture lane but there were no pending gestures. ' +
|
||||
'React should not have started a render in this case. This is a bug in React.',
|
||||
);
|
||||
}
|
||||
deleteScheduledGesture(root, finishedGesture);
|
||||
// TODO: Run the gesture
|
||||
}
|
||||
|
||||
function makeErrorInfo(componentStack: ?string) {
|
||||
const errorInfo = {
|
||||
componentStack,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import type {
|
|||
Awaited,
|
||||
ReactComponentInfo,
|
||||
ReactDebugInfo,
|
||||
StartGesture,
|
||||
} from 'shared/ReactTypes';
|
||||
import type {WorkTag} from './ReactWorkTags';
|
||||
import type {TypeOfMode} from './ReactTypeOfMode';
|
||||
|
|
@ -38,6 +39,7 @@ import type {
|
|||
import type {ConcurrentUpdate} from './ReactFiberConcurrentUpdates';
|
||||
import type {ComponentStackNode} from 'react-server/src/ReactFizzComponentStack';
|
||||
import type {ThenableState} from './ReactFiberThenable';
|
||||
import type {ScheduledGesture} from './ReactFiberGestureScheduler';
|
||||
|
||||
// Unwind Circular: moved from ReactFiberHooks.old
|
||||
export type HookType =
|
||||
|
|
@ -60,7 +62,8 @@ export type HookType =
|
|||
| 'useCacheRefresh'
|
||||
| 'useOptimistic'
|
||||
| 'useFormState'
|
||||
| 'useActionState';
|
||||
| 'useActionState'
|
||||
| 'useSwipeTransition';
|
||||
|
||||
export type ContextDependency<T> = {
|
||||
context: ReactContext<T>,
|
||||
|
|
@ -279,6 +282,9 @@ type BaseFiberRootProperties = {
|
|||
) => void,
|
||||
|
||||
formState: ReactFormState<any, any> | null,
|
||||
|
||||
// enableSwipeTransition only
|
||||
gestures: null | ScheduledGesture,
|
||||
};
|
||||
|
||||
// The following attributes are only used by DevTools and are only present in DEV builds.
|
||||
|
|
@ -442,6 +448,12 @@ export type Dispatcher = {
|
|||
initialState: Awaited<S>,
|
||||
permalink?: string,
|
||||
) => [Awaited<S>, (P) => void, boolean],
|
||||
// TODO: Non-nullable once `enableSwipeTransition` is on everywhere.
|
||||
useSwipeTransition?: <T>(
|
||||
previous: T,
|
||||
current: T,
|
||||
next: T,
|
||||
) => [T, StartGesture],
|
||||
};
|
||||
|
||||
export type AsyncDispatcher = {
|
||||
|
|
|
|||
42
packages/react-server/src/ReactFizzHooks.js
vendored
42
packages/react-server/src/ReactFizzHooks.js
vendored
|
|
@ -16,6 +16,7 @@ import type {
|
|||
Usable,
|
||||
ReactCustomFormAction,
|
||||
Awaited,
|
||||
StartGesture,
|
||||
} from 'shared/ReactTypes';
|
||||
|
||||
import type {ResumableState} from './ReactFizzConfig';
|
||||
|
|
@ -38,7 +39,10 @@ import {
|
|||
} from './ReactFizzConfig';
|
||||
import {createFastHash} from './ReactServerStreamConfig';
|
||||
|
||||
import {enableUseEffectEventHook} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
enableUseEffectEventHook,
|
||||
enableSwipeTransition,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import is from 'shared/objectIs';
|
||||
import {
|
||||
REACT_CONTEXT_TYPE,
|
||||
|
|
@ -795,6 +799,19 @@ function useMemoCache(size: number): Array<mixed> {
|
|||
return data;
|
||||
}
|
||||
|
||||
function unsupportedStartGesture() {
|
||||
throw new Error('startGesture cannot be called during server rendering.');
|
||||
}
|
||||
|
||||
function useSwipeTransition<T>(
|
||||
previous: T,
|
||||
current: T,
|
||||
next: T,
|
||||
): [T, StartGesture] {
|
||||
resolveCurrentlyRenderingComponent();
|
||||
return [current, unsupportedStartGesture];
|
||||
}
|
||||
|
||||
function noop(): void {}
|
||||
|
||||
function clientHookNotSupported() {
|
||||
|
|
@ -837,25 +854,25 @@ export const HooksDispatcher: Dispatcher = supportsClientAPIs
|
|||
: {
|
||||
readContext,
|
||||
use,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect: clientHookNotSupported,
|
||||
useImperativeHandle: clientHookNotSupported,
|
||||
useInsertionEffect: clientHookNotSupported,
|
||||
useLayoutEffect: clientHookNotSupported,
|
||||
useMemo,
|
||||
useReducer: clientHookNotSupported,
|
||||
useRef: clientHookNotSupported,
|
||||
useState: clientHookNotSupported,
|
||||
useInsertionEffect: clientHookNotSupported,
|
||||
useLayoutEffect: clientHookNotSupported,
|
||||
useCallback,
|
||||
useImperativeHandle: clientHookNotSupported,
|
||||
useEffect: clientHookNotSupported,
|
||||
useDebugValue: noop,
|
||||
useDeferredValue: clientHookNotSupported,
|
||||
useTransition: clientHookNotSupported,
|
||||
useId,
|
||||
useSyncExternalStore: clientHookNotSupported,
|
||||
useOptimistic,
|
||||
useActionState,
|
||||
useFormState: useActionState,
|
||||
useId,
|
||||
useHostTransitionStatus,
|
||||
useFormState: useActionState,
|
||||
useActionState,
|
||||
useOptimistic,
|
||||
useMemoCache,
|
||||
useCacheRefresh,
|
||||
};
|
||||
|
|
@ -863,6 +880,11 @@ export const HooksDispatcher: Dispatcher = supportsClientAPIs
|
|||
if (enableUseEffectEventHook) {
|
||||
HooksDispatcher.useEffectEvent = useEffectEvent;
|
||||
}
|
||||
if (enableSwipeTransition) {
|
||||
HooksDispatcher.useSwipeTransition = supportsClientAPIs
|
||||
? useSwipeTransition
|
||||
: clientHookNotSupported;
|
||||
}
|
||||
|
||||
export let currentResumableState: null | ResumableState = (null: any);
|
||||
export function setCurrentResumableState(
|
||||
|
|
|
|||
45
packages/react-server/src/ReactFlightHooks.js
vendored
45
packages/react-server/src/ReactFlightHooks.js
vendored
|
|
@ -17,6 +17,10 @@ import {
|
|||
} from 'shared/ReactSymbols';
|
||||
import {createThenableState, trackUsedThenable} from './ReactFlightThenable';
|
||||
import {isClientReference} from './ReactFlightServerConfig';
|
||||
import {
|
||||
enableUseEffectEventHook,
|
||||
enableSwipeTransition,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
|
||||
let currentRequest = null;
|
||||
let thenableIndexCounter = 0;
|
||||
|
|
@ -58,33 +62,32 @@ export function getThenableStateAfterSuspending(): ThenableState {
|
|||
}
|
||||
|
||||
export const HooksDispatcher: Dispatcher = {
|
||||
useMemo<T>(nextCreate: () => T): T {
|
||||
return nextCreate();
|
||||
},
|
||||
readContext: (unsupportedContext: any),
|
||||
|
||||
use,
|
||||
useCallback<T>(callback: T): T {
|
||||
return callback;
|
||||
},
|
||||
useDebugValue(): void {},
|
||||
useDeferredValue: (unsupportedHook: any),
|
||||
useTransition: (unsupportedHook: any),
|
||||
readContext: (unsupportedContext: any),
|
||||
useContext: (unsupportedContext: any),
|
||||
useEffect: (unsupportedHook: any),
|
||||
useImperativeHandle: (unsupportedHook: any),
|
||||
useLayoutEffect: (unsupportedHook: any),
|
||||
useInsertionEffect: (unsupportedHook: any),
|
||||
useMemo<T>(nextCreate: () => T): T {
|
||||
return nextCreate();
|
||||
},
|
||||
useReducer: (unsupportedHook: any),
|
||||
useRef: (unsupportedHook: any),
|
||||
useState: (unsupportedHook: any),
|
||||
useInsertionEffect: (unsupportedHook: any),
|
||||
useLayoutEffect: (unsupportedHook: any),
|
||||
useImperativeHandle: (unsupportedHook: any),
|
||||
useEffect: (unsupportedHook: any),
|
||||
useDebugValue(): void {},
|
||||
useDeferredValue: (unsupportedHook: any),
|
||||
useTransition: (unsupportedHook: any),
|
||||
useSyncExternalStore: (unsupportedHook: any),
|
||||
useId,
|
||||
useHostTransitionStatus: (unsupportedHook: any),
|
||||
useOptimistic: (unsupportedHook: any),
|
||||
useFormState: (unsupportedHook: any),
|
||||
useActionState: (unsupportedHook: any),
|
||||
useSyncExternalStore: (unsupportedHook: any),
|
||||
useCacheRefresh(): <T>(?() => T, ?T) => void {
|
||||
return unsupportedRefresh;
|
||||
},
|
||||
useOptimistic: (unsupportedHook: any),
|
||||
useMemoCache(size: number): Array<any> {
|
||||
const data = new Array<any>(size);
|
||||
for (let i = 0; i < size; i++) {
|
||||
|
|
@ -92,8 +95,16 @@ export const HooksDispatcher: Dispatcher = {
|
|||
}
|
||||
return data;
|
||||
},
|
||||
use,
|
||||
useCacheRefresh(): <T>(?() => T, ?T) => void {
|
||||
return unsupportedRefresh;
|
||||
},
|
||||
};
|
||||
if (enableUseEffectEventHook) {
|
||||
HooksDispatcher.useEffectEvent = (unsupportedHook: any);
|
||||
}
|
||||
if (enableSwipeTransition) {
|
||||
HooksDispatcher.useSwipeTransition = (unsupportedHook: any);
|
||||
}
|
||||
|
||||
function unsupportedHook(): void {
|
||||
throw new Error('This Hook is not supported in Server Components.');
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ export {
|
|||
unstable_getCacheForType,
|
||||
unstable_SuspenseList,
|
||||
unstable_ViewTransition,
|
||||
unstable_useSwipeTransition,
|
||||
unstable_addTransitionType,
|
||||
unstable_useCacheRefresh,
|
||||
useId,
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ export {
|
|||
unstable_getCacheForType,
|
||||
unstable_SuspenseList,
|
||||
unstable_ViewTransition,
|
||||
unstable_useSwipeTransition,
|
||||
unstable_addTransitionType,
|
||||
unstable_useCacheRefresh,
|
||||
useId,
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ import {
|
|||
use,
|
||||
useOptimistic,
|
||||
useActionState,
|
||||
useSwipeTransition,
|
||||
} from './ReactHooks';
|
||||
import ReactSharedInternals from './ReactSharedInternalsClient';
|
||||
import {startTransition} from './ReactStartTransition';
|
||||
|
|
@ -126,7 +127,10 @@ export {
|
|||
// enableViewTransition
|
||||
REACT_VIEW_TRANSITION_TYPE as unstable_ViewTransition,
|
||||
addTransitionType as unstable_addTransitionType,
|
||||
// enableSwipeTransition
|
||||
useSwipeTransition as unstable_useSwipeTransition,
|
||||
// DEV-only
|
||||
useId,
|
||||
act, // DEV-only
|
||||
captureOwnerStack, // DEV-only
|
||||
act,
|
||||
captureOwnerStack,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,12 +13,16 @@ import type {
|
|||
StartTransitionOptions,
|
||||
Usable,
|
||||
Awaited,
|
||||
StartGesture,
|
||||
} from 'shared/ReactTypes';
|
||||
import {REACT_CONSUMER_TYPE} from 'shared/ReactSymbols';
|
||||
|
||||
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||
|
||||
import {enableUseEffectCRUDOverload} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
enableUseEffectCRUDOverload,
|
||||
enableSwipeTransition,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
|
||||
type BasicStateAction<S> = (S => S) | S;
|
||||
type Dispatch<A> = A => void;
|
||||
|
|
@ -261,3 +265,16 @@ export function useActionState<S, P>(
|
|||
const dispatcher = resolveDispatcher();
|
||||
return dispatcher.useActionState(action, initialState, permalink);
|
||||
}
|
||||
|
||||
export function useSwipeTransition<T>(
|
||||
previous: T,
|
||||
current: T,
|
||||
next: T,
|
||||
): [T, StartGesture] {
|
||||
if (!enableSwipeTransition) {
|
||||
throw new Error('Not implemented.');
|
||||
}
|
||||
const dispatcher = resolveDispatcher();
|
||||
// $FlowFixMe[not-a-function] This is unstable, thus optional
|
||||
return dispatcher.useSwipeTransition(previous, current, next);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,6 +92,8 @@ export const enableHalt = __EXPERIMENTAL__;
|
|||
|
||||
export const enableViewTransition = __EXPERIMENTAL__;
|
||||
|
||||
export const enableSwipeTransition = __EXPERIMENTAL__;
|
||||
|
||||
/**
|
||||
* Switches Fiber creation to a simple object instead of a constructor.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -168,6 +168,12 @@ export type ReactFormState<S, ReferenceId> = [
|
|||
number /* number of bound arguments */,
|
||||
];
|
||||
|
||||
// Intrinsic GestureProvider. This type varies by Environment whether a particular
|
||||
// renderer supports it.
|
||||
export type GestureProvider = AnimationTimeline; // TODO: More provider types.
|
||||
|
||||
export type StartGesture = (gestureProvider: GestureProvider) => () => void;
|
||||
|
||||
export type Awaited<T> = T extends null | void
|
||||
? T // special case for `null | undefined` when not in `--strictNullChecks` mode
|
||||
: T extends Object // `await` only unwraps object types with a callable then. Non-object types are not unwrapped.
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ export const enableHydrationLaneScheduling = true;
|
|||
export const enableYieldingBeforePassive = false;
|
||||
export const enableThrottledScheduling = false;
|
||||
export const enableViewTransition = false;
|
||||
export const enableSwipeTransition = false;
|
||||
|
||||
// Flow magic to verify the exports of this file match the original version.
|
||||
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ export const enableYieldingBeforePassive = false;
|
|||
|
||||
export const enableThrottledScheduling = false;
|
||||
export const enableViewTransition = false;
|
||||
export const enableSwipeTransition = false;
|
||||
export const enableFastAddPropertiesInDiffing = false;
|
||||
export const enableLazyPublicInstanceInFabric = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ export const enableYieldingBeforePassive = true;
|
|||
|
||||
export const enableThrottledScheduling = false;
|
||||
export const enableViewTransition = false;
|
||||
export const enableSwipeTransition = false;
|
||||
export const enableFastAddPropertiesInDiffing = true;
|
||||
export const enableLazyPublicInstanceInFabric = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ export const enableHydrationLaneScheduling = true;
|
|||
export const enableYieldingBeforePassive = false;
|
||||
export const enableThrottledScheduling = false;
|
||||
export const enableViewTransition = false;
|
||||
export const enableSwipeTransition = false;
|
||||
export const enableRemoveConsolePatches = false;
|
||||
export const enableFastAddPropertiesInDiffing = false;
|
||||
export const enableLazyPublicInstanceInFabric = false;
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ export const enableYieldingBeforePassive = false;
|
|||
|
||||
export const enableThrottledScheduling = false;
|
||||
export const enableViewTransition = false;
|
||||
export const enableSwipeTransition = false;
|
||||
export const enableRemoveConsolePatches = false;
|
||||
export const enableFastAddPropertiesInDiffing = false;
|
||||
export const enableLazyPublicInstanceInFabric = false;
|
||||
|
|
|
|||
|
|
@ -112,5 +112,7 @@ export const enableShallowPropDiffing = false;
|
|||
|
||||
export const enableLazyPublicInstanceInFabric = false;
|
||||
|
||||
export const enableSwipeTransition = false;
|
||||
|
||||
// Flow magic to verify the exports of this file match the original version.
|
||||
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
|
||||
|
|
|
|||
|
|
@ -531,5 +531,7 @@
|
|||
"543": "Expected a ResourceEffectUpdate to be pushed together with ResourceEffectIdentity. This is a bug in React.",
|
||||
"544": "Found a pair with an auto name. This is a bug in React.",
|
||||
"545": "The %s tag may only be rendered once.",
|
||||
"546": "useEffect CRUD overload is not enabled in this build of React."
|
||||
"546": "useEffect CRUD overload is not enabled in this build of React.",
|
||||
"547": "startGesture cannot be called during server rendering.",
|
||||
"548": "Finished rendering the gesture lane but there were no pending gestures. React should not have started a render in this case. This is a bug in React."
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user