From ce4fe7dad47a56717eb18d131a3fc7a3b21be8dc Mon Sep 17 00:00:00 2001 From: Josh Story Date: Mon, 8 Apr 2024 08:53:17 -0700 Subject: [PATCH] [Fiber] Move updatePriority tracking to renderers (#28751) Currently updatePriority is tracked in the reconciler. `flushSync` is going to be implemented reconciler agnostic soon and we need to move the tracking of this state to the renderer and out of reconciler. This change implements new renderer bin dings for getCurrentUpdatePriority and setCurrentUpdatePriority. I was originally going to have the getter also do the event priority defaulting using window.event so we eliminate getCur rentEventPriority but this makes all the callsites where we store the true current updatePriority on the stack harder to work with so for now they remain separate. I also moved runWithPriority to the renderer since it really belongs whereever the state is being managed and it is only currently exposed in the DOM renderer. Additionally the current update priority is not stored on ReactDOMSharedInternals. While not particularly meaningful in this change it opens the door to implementing `flushSync` outside of the reconciler --- packages/react-art/src/ReactFiberConfigART.js | 21 ++++++- .../src/client/ReactDOMUpdatePriority.js | 55 +++++++++++++++++++ .../src/client/ReactFiberConfigDOM.js | 16 ++---- .../src/events/ReactDOMEventListener.js | 10 ++-- .../src/events/ReactDOMEventReplaying.js | 8 +-- .../react-dom/src/ReactDOMSharedInternals.js | 11 +++- packages/react-dom/src/client/ReactDOM.js | 2 +- .../src/ReactFiberConfigFabric.js | 16 +++++- .../src/ReactFiberConfigNative.js | 15 ++++- .../src/createReactNoop.js | 32 ++++++++++- .../src/ReactEventPriorities.js | 25 ++------- .../src/ReactFiberBeginWork.js | 2 +- .../src/ReactFiberClassUpdateQueue.js | 2 +- .../src/ReactFiberCompleteWork.js | 2 +- .../react-reconciler/src/ReactFiberHooks.js | 8 ++- .../react-reconciler/src/ReactFiberLane.js | 2 +- .../src/ReactFiberReconciler.js | 6 -- .../src/ReactFiberWorkLoop.js | 47 +++++----------- .../src/ReactReconcilerConstants.js | 1 + .../ReactFiberHostContext-test.internal.js | 15 ++++- .../src/forks/ReactFiberConfig.custom.js | 4 +- .../src/ReactFiberConfigTestHost.js | 15 ++++- 22 files changed, 217 insertions(+), 98 deletions(-) create mode 100644 packages/react-dom-bindings/src/client/ReactDOMUpdatePriority.js diff --git a/packages/react-art/src/ReactFiberConfigART.js b/packages/react-art/src/ReactFiberConfigART.js index d0ae53019cac6..0900c50f7e94e 100644 --- a/packages/react-art/src/ReactFiberConfigART.js +++ b/packages/react-art/src/ReactFiberConfigART.js @@ -5,12 +5,17 @@ * LICENSE file in the root directory of this source tree. */ +import type {EventPriority} from 'react-reconciler/src/ReactEventPriorities'; + import Transform from 'art/core/transform'; import Mode from 'art/modes/current'; import {TYPES, EVENT_TYPES, childrenAsString} from './ReactARTInternals'; -import {DefaultEventPriority} from 'react-reconciler/src/ReactEventPriorities'; +import { + DefaultEventPriority, + NoEventPriority, +} from 'react-reconciler/src/ReactEventPriorities'; const pooledTransform = new Transform(); @@ -336,8 +341,18 @@ export function shouldSetTextContent(type, props) { ); } -export function getCurrentEventPriority() { - return DefaultEventPriority; +let currentUpdatePriority: EventPriority = NoEventPriority; + +export function setCurrentUpdatePriority(newPriority: EventPriority): void { + currentUpdatePriority = newPriority; +} + +export function getCurrentUpdatePriority(): EventPriority { + return currentUpdatePriority; +} + +export function resolveUpdatePriority(): EventPriority { + return currentUpdatePriority || DefaultEventPriority; } export function shouldAttemptEagerTransition() { diff --git a/packages/react-dom-bindings/src/client/ReactDOMUpdatePriority.js b/packages/react-dom-bindings/src/client/ReactDOMUpdatePriority.js new file mode 100644 index 0000000000000..a0c4273bec604 --- /dev/null +++ b/packages/react-dom-bindings/src/client/ReactDOMUpdatePriority.js @@ -0,0 +1,55 @@ +/** + * 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 {EventPriority} from 'react-reconciler/src/ReactEventPriorities'; + +import {getEventPriority} from '../events/ReactDOMEventListener'; +import { + NoEventPriority, + DefaultEventPriority, +} from 'react-reconciler/src/ReactEventPriorities'; + +import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals'; + +export function setCurrentUpdatePriority( + newPriority: EventPriority, + // Closure will consistently not inline this function when it has arity 1 + // however when it has arity 2 even if the second arg is omitted at every + // callsite it seems to inline it even when the internal length of the function + // is much longer. I hope this is consistent enough to rely on across builds + IntentionallyUnusedArgument?: empty, +): void { + ReactDOMSharedInternals.up = newPriority; +} + +export function getCurrentUpdatePriority(): EventPriority { + return ReactDOMSharedInternals.up; +} + +export function resolveUpdatePriority(): EventPriority { + const updatePriority = ReactDOMSharedInternals.up; + if (updatePriority !== NoEventPriority) { + return updatePriority; + } + const currentEvent = window.event; + if (currentEvent === undefined) { + return DefaultEventPriority; + } + return getEventPriority(currentEvent.type); +} + +export function runWithPriority(priority: EventPriority, fn: () => T): T { + const previousPriority = getCurrentUpdatePriority(); + try { + setCurrentUpdatePriority(priority); + return fn(); + } finally { + setCurrentUpdatePriority(previousPriority); + } +} diff --git a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js index 9b9f41a92d374..504172309f9c5 100644 --- a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js +++ b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js @@ -7,7 +7,6 @@ * @flow */ -import type {EventPriority} from 'react-reconciler/src/ReactEventPriorities'; import type {DOMEventName} from '../events/DOMEventNames'; import type {Fiber, FiberRoot} from 'react-reconciler/src/ReactInternalTypes'; import type { @@ -29,11 +28,15 @@ import type { import {NotPending} from 'react-dom-bindings/src/shared/ReactDOMFormActions'; import {getCurrentRootHostContainer} from 'react-reconciler/src/ReactFiberHostContext'; -import {DefaultEventPriority} from 'react-reconciler/src/ReactEventPriorities'; import hasOwnProperty from 'shared/hasOwnProperty'; import {checkAttributeStringCoercion} from 'shared/CheckStringCoercion'; +export { + setCurrentUpdatePriority, + getCurrentUpdatePriority, + resolveUpdatePriority, +} from './ReactDOMUpdatePriority'; import { precacheFiberNode, updateFiberProps, @@ -69,7 +72,6 @@ import { import { isEnabled as ReactBrowserEventEmitterIsEnabled, setEnabled as ReactBrowserEventEmitterSetEnabled, - getEventPriority, } from '../events/ReactDOMEventListener'; import {SVG_NAMESPACE, MATH_NAMESPACE} from './DOMNamespaces'; import { @@ -572,14 +574,6 @@ export function createTextInstance( return textNode; } -export function getCurrentEventPriority(): EventPriority { - const currentEvent = window.event; - if (currentEvent === undefined) { - return DefaultEventPriority; - } - return getEventPriority(currentEvent.type); -} - let currentPopstateTransitionEvent: Event | null = null; export function shouldAttemptEagerTransition(): boolean { const event = window.event; diff --git a/packages/react-dom-bindings/src/events/ReactDOMEventListener.js b/packages/react-dom-bindings/src/events/ReactDOMEventListener.js index 237cde231176b..525f7bd358c90 100644 --- a/packages/react-dom-bindings/src/events/ReactDOMEventListener.js +++ b/packages/react-dom-bindings/src/events/ReactDOMEventListener.js @@ -34,6 +34,10 @@ import { } from '../client/ReactDOMComponentTree'; import {dispatchEventForPluginEventSystem} from './DOMPluginEventSystem'; +import { + getCurrentUpdatePriority, + setCurrentUpdatePriority, +} from '../client/ReactDOMUpdatePriority'; import { getCurrentPriorityLevel as getCurrentSchedulerPriorityLevel, @@ -48,8 +52,6 @@ import { ContinuousEventPriority, DefaultEventPriority, IdleEventPriority, - getCurrentUpdatePriority, - setCurrentUpdatePriority, } from 'react-reconciler/src/ReactEventPriorities'; import ReactSharedInternals from 'shared/ReactSharedInternals'; import {isRootDehydrated} from 'react-reconciler/src/ReactFiberShellHydration'; @@ -115,9 +117,9 @@ function dispatchDiscreteEvent( container: EventTarget, nativeEvent: AnyNativeEvent, ) { - const previousPriority = getCurrentUpdatePriority(); const prevTransition = ReactCurrentBatchConfig.transition; ReactCurrentBatchConfig.transition = null; + const previousPriority = getCurrentUpdatePriority(); try { setCurrentUpdatePriority(DiscreteEventPriority); dispatchEvent(domEventName, eventSystemFlags, container, nativeEvent); @@ -133,9 +135,9 @@ function dispatchContinuousEvent( container: EventTarget, nativeEvent: AnyNativeEvent, ) { - const previousPriority = getCurrentUpdatePriority(); const prevTransition = ReactCurrentBatchConfig.transition; ReactCurrentBatchConfig.transition = null; + const previousPriority = getCurrentUpdatePriority(); try { setCurrentUpdatePriority(ContinuousEventPriority); dispatchEvent(domEventName, eventSystemFlags, container, nativeEvent); diff --git a/packages/react-dom-bindings/src/events/ReactDOMEventReplaying.js b/packages/react-dom-bindings/src/events/ReactDOMEventReplaying.js index 5a3e6919cb364..fc5e59eb839e5 100644 --- a/packages/react-dom-bindings/src/events/ReactDOMEventReplaying.js +++ b/packages/react-dom-bindings/src/events/ReactDOMEventReplaying.js @@ -37,15 +37,15 @@ import {HostRoot, SuspenseComponent} from 'react-reconciler/src/ReactWorkTags'; import {isHigherEventPriority} from 'react-reconciler/src/ReactEventPriorities'; import {isRootDehydrated} from 'react-reconciler/src/ReactFiberShellHydration'; import {dispatchReplayedFormAction} from './plugins/FormActionEventPlugin'; +import { + getCurrentUpdatePriority, + runWithPriority as attemptHydrationAtPriority, +} from '../client/ReactDOMUpdatePriority'; import { attemptContinuousHydration, attemptHydrationAtCurrentPriority, } from 'react-reconciler/src/ReactFiberReconciler'; -import { - runWithPriority as attemptHydrationAtPriority, - getCurrentUpdatePriority, -} from 'react-reconciler/src/ReactEventPriorities'; // TODO: Upgrade this definition once we're on a newer version of Flow that // has this definition built-in. diff --git a/packages/react-dom/src/ReactDOMSharedInternals.js b/packages/react-dom/src/ReactDOMSharedInternals.js index 7bd080ce48240..f2dd23c26660f 100644 --- a/packages/react-dom/src/ReactDOMSharedInternals.js +++ b/packages/react-dom/src/ReactDOMSharedInternals.js @@ -7,8 +7,11 @@ * @flow */ +import type {EventPriority} from 'react-reconciler/src/ReactEventPriorities'; import type {HostDispatcher} from './shared/ReactDOMTypes'; +import {NoEventPriority} from 'react-reconciler/src/ReactEventPriorities'; + type InternalsType = { usingClientEntryPoint: boolean, Events: [any, any, any, any, any, any], @@ -20,6 +23,7 @@ type InternalsType = { | (( componentOrElement: React$Component, ) => null | Element | Text), + up /* currentUpdatePriority */: EventPriority, }; function noop() {} @@ -34,13 +38,14 @@ const DefaultDispatcher: HostDispatcher = { preinitModuleScript: noop, }; -const Internals: InternalsType = ({ +const Internals: InternalsType = { usingClientEntryPoint: false, - Events: null, + Events: (null: any), ReactDOMCurrentDispatcher: { current: DefaultDispatcher, }, findDOMNode: null, -}: any); + up /* currentUpdatePriority */: NoEventPriority, +}; export default Internals; diff --git a/packages/react-dom/src/client/ReactDOM.js b/packages/react-dom/src/client/ReactDOM.js index e0cbe30a079f3..04b59d618aeb1 100644 --- a/packages/react-dom/src/client/ReactDOM.js +++ b/packages/react-dom/src/client/ReactDOM.js @@ -20,6 +20,7 @@ import { isValidContainer, } from './ReactDOMRoot'; import {createEventHandle} from 'react-dom-bindings/src/client/ReactDOMEventHandle'; +import {runWithPriority} from 'react-dom-bindings/src/client/ReactDOMUpdatePriority'; import { flushSync as flushSyncWithoutWarningIfAlreadyRendering, @@ -27,7 +28,6 @@ import { injectIntoDevTools, findHostInstance, } from 'react-reconciler/src/ReactFiberReconciler'; -import {runWithPriority} from 'react-reconciler/src/ReactEventPriorities'; import {createPortal as createPortalImpl} from 'react-reconciler/src/ReactPortal'; import {canUseDOM} from 'shared/ExecutionEnvironment'; import ReactVersion from 'shared/ReactVersion'; diff --git a/packages/react-native-renderer/src/ReactFiberConfigFabric.js b/packages/react-native-renderer/src/ReactFiberConfigFabric.js index e0f46a07822c3..0a4f30c901531 100644 --- a/packages/react-native-renderer/src/ReactFiberConfigFabric.js +++ b/packages/react-native-renderer/src/ReactFiberConfigFabric.js @@ -15,6 +15,7 @@ import type { import {create, diff} from './ReactNativeAttributePayload'; import {dispatchEvent} from './ReactFabricEventEmitter'; import { + NoEventPriority, DefaultEventPriority, DiscreteEventPriority, type EventPriority, @@ -311,7 +312,20 @@ export function shouldSetTextContent(type: string, props: Props): boolean { return false; } -export function getCurrentEventPriority(): EventPriority { +let currentUpdatePriority: EventPriority = NoEventPriority; +export function setCurrentUpdatePriority(newPriority: EventPriority): void { + currentUpdatePriority = newPriority; +} + +export function getCurrentUpdatePriority(): EventPriority { + return currentUpdatePriority; +} + +export function resolveUpdatePriority(): EventPriority { + if (currentUpdatePriority !== NoEventPriority) { + return currentUpdatePriority; + } + const currentEventPriority = fabricGetCurrentEventPriority ? fabricGetCurrentEventPriority() : null; diff --git a/packages/react-native-renderer/src/ReactFiberConfigNative.js b/packages/react-native-renderer/src/ReactFiberConfigNative.js index b61c8ef292a40..c24c3a0c95f4f 100644 --- a/packages/react-native-renderer/src/ReactFiberConfigNative.js +++ b/packages/react-native-renderer/src/ReactFiberConfigNative.js @@ -26,6 +26,7 @@ import ReactNativeFiberHostComponent from './ReactNativeFiberHostComponent'; import { DefaultEventPriority, + NoEventPriority, type EventPriority, } from 'react-reconciler/src/ReactEventPriorities'; import type {Fiber} from 'react-reconciler/src/ReactInternalTypes'; @@ -253,7 +254,19 @@ export function shouldSetTextContent(type: string, props: Props): boolean { return false; } -export function getCurrentEventPriority(): EventPriority { +let currentUpdatePriority: EventPriority = NoEventPriority; +export function setCurrentUpdatePriority(newPriority: EventPriority): void { + currentUpdatePriority = newPriority; +} + +export function getCurrentUpdatePriority(): EventPriority { + return currentUpdatePriority; +} + +export function resolveUpdatePriority(): EventPriority { + if (currentUpdatePriority !== NoEventPriority) { + return currentUpdatePriority; + } return DefaultEventPriority; } diff --git a/packages/react-noop-renderer/src/createReactNoop.js b/packages/react-noop-renderer/src/createReactNoop.js index 18bd7dbdb01c7..86c04f961d788 100644 --- a/packages/react-noop-renderer/src/createReactNoop.js +++ b/packages/react-noop-renderer/src/createReactNoop.js @@ -21,12 +21,14 @@ import type { import type {UpdateQueue} from 'react-reconciler/src/ReactFiberClassUpdateQueue'; import type {ReactNodeList} from 'shared/ReactTypes'; import type {RootTag} from 'react-reconciler/src/ReactRootTags'; +import type {EventPriority} from 'react-reconciler/src/ReactEventPriorities'; import * as Scheduler from 'scheduler/unstable_mock'; import {REACT_FRAGMENT_TYPE, REACT_ELEMENT_TYPE} from 'shared/ReactSymbols'; import isArray from 'shared/isArray'; import {checkPropStringCoercion} from 'shared/CheckStringCoercion'; import { + NoEventPriority, DefaultEventPriority, IdleEventPriority, ConcurrentRoot, @@ -512,7 +514,13 @@ function createReactNoop(reconciler: Function, useMutation: boolean) { resetAfterCommit(): void {}, - getCurrentEventPriority() { + setCurrentUpdatePriority, + getCurrentUpdatePriority, + + resolveUpdatePriority() { + if (currentUpdatePriority !== NoEventPriority) { + return currentUpdatePriority; + } return currentEventPriority; }, @@ -786,6 +794,15 @@ function createReactNoop(reconciler: Function, useMutation: boolean) { const roots = new Map(); const DEFAULT_ROOT_ID = ''; + let currentUpdatePriority = NoEventPriority; + function setCurrentUpdatePriority(newPriority: EventPriority): void { + currentUpdatePriority = newPriority; + } + + function getCurrentUpdatePriority(): EventPriority { + return currentUpdatePriority; + } + let currentEventPriority = DefaultEventPriority; function createJSXElementForTestComparison(type, props) { @@ -1223,7 +1240,18 @@ function createReactNoop(reconciler: Function, useMutation: boolean) { return Scheduler.unstable_flushExpired(); }, - unstable_runWithPriority: NoopRenderer.runWithPriority, + unstable_runWithPriority: function runWithPriority( + priority: EventPriority, + fn: () => T, + ): T { + const previousPriority = getCurrentUpdatePriority(); + try { + setCurrentUpdatePriority(priority); + return fn(); + } finally { + setCurrentUpdatePriority(previousPriority); + } + }, batchedUpdates: NoopRenderer.batchedUpdates, diff --git a/packages/react-reconciler/src/ReactEventPriorities.js b/packages/react-reconciler/src/ReactEventPriorities.js index 6ae2f85f4b6ed..e28223e9d89b1 100644 --- a/packages/react-reconciler/src/ReactEventPriorities.js +++ b/packages/react-reconciler/src/ReactEventPriorities.js @@ -21,31 +21,12 @@ import { export opaque type EventPriority = Lane; +export const NoEventPriority: EventPriority = NoLane; export const DiscreteEventPriority: EventPriority = SyncLane; export const ContinuousEventPriority: EventPriority = InputContinuousLane; export const DefaultEventPriority: EventPriority = DefaultLane; export const IdleEventPriority: EventPriority = IdleLane; -let currentUpdatePriority: EventPriority = NoLane; - -export function getCurrentUpdatePriority(): EventPriority { - return currentUpdatePriority; -} - -export function setCurrentUpdatePriority(newPriority: EventPriority) { - currentUpdatePriority = newPriority; -} - -export function runWithPriority(priority: EventPriority, fn: () => T): T { - const previousPriority = currentUpdatePriority; - try { - currentUpdatePriority = priority; - return fn(); - } finally { - currentUpdatePriority = previousPriority; - } -} - export function higherEventPriority( a: EventPriority, b: EventPriority, @@ -67,6 +48,10 @@ export function isHigherEventPriority( return a !== 0 && a < b; } +export function eventPriorityToLane(updatePriority: EventPriority): Lane { + return updatePriority; +} + export function lanesToEventPriority(lanes: Lanes): EventPriority { const lane = getHighestPriorityLane(lanes); if (!isHigherEventPriority(DiscreteEventPriority, lane)) { diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index cb4d299a0af2e..ef0f47f2fa111 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -679,7 +679,7 @@ function updateOffscreenComponent( // pending work. We can't read `childLanes` from the current Offscreen // fiber because we reset it when it was deferred; however, we can read // the pending lanes from the child fibers. - let currentChildLanes = NoLanes; + let currentChildLanes: Lanes = NoLanes; while (currentChild !== null) { currentChildLanes = mergeLanes( mergeLanes(currentChildLanes, currentChild.lanes), diff --git a/packages/react-reconciler/src/ReactFiberClassUpdateQueue.js b/packages/react-reconciler/src/ReactFiberClassUpdateQueue.js index d7a1efbb47f4a..7945e4fb1c915 100644 --- a/packages/react-reconciler/src/ReactFiberClassUpdateQueue.js +++ b/packages/react-reconciler/src/ReactFiberClassUpdateQueue.js @@ -556,7 +556,7 @@ export function processUpdateQueue( let newState = queue.baseState; // TODO: Don't need to accumulate this. Instead, we can remove renderLanes // from the original lanes. - let newLanes = NoLanes; + let newLanes: Lanes = NoLanes; let newBaseState = null; let newFirstBaseUpdate = null; diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.js b/packages/react-reconciler/src/ReactFiberCompleteWork.js index 51e21b8d2f480..319c8f5dab5d1 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.js @@ -742,7 +742,7 @@ function bubbleProperties(completedWork: Fiber) { completedWork.alternate !== null && completedWork.alternate.child === completedWork.child; - let newChildLanes = NoLanes; + let newChildLanes: Lanes = NoLanes; let subtreeFlags = NoFlags; if (!didBailout) { diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js index 0d86c2e1c5750..0d4e803a7a130 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.js +++ b/packages/react-reconciler/src/ReactFiberHooks.js @@ -27,7 +27,11 @@ import type {HookFlags} from './ReactHookEffectTags'; import type {Flags} from './ReactFiberFlags'; import type {TransitionStatus} from './ReactFiberConfig'; -import {NotPendingTransition as NoPendingHostTransition} from './ReactFiberConfig'; +import { + NotPendingTransition as NoPendingHostTransition, + setCurrentUpdatePriority, + getCurrentUpdatePriority, +} from './ReactFiberConfig'; import ReactSharedInternals from 'shared/ReactSharedInternals'; import { enableDebugTracing, @@ -74,8 +78,6 @@ import { } from './ReactFiberLane'; import { ContinuousEventPriority, - getCurrentUpdatePriority, - setCurrentUpdatePriority, higherEventPriority, } from './ReactEventPriorities'; import {readContext, checkIfContextChanged} from './ReactFiberNewContext'; diff --git a/packages/react-reconciler/src/ReactFiberLane.js b/packages/react-reconciler/src/ReactFiberLane.js index 7e056f2e71068..cde23ef9a6249 100644 --- a/packages/react-reconciler/src/ReactFiberLane.js +++ b/packages/react-reconciler/src/ReactFiberLane.js @@ -223,7 +223,7 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes { return NoLanes; } - let nextLanes = NoLanes; + let nextLanes: Lanes = NoLanes; const suspendedLanes = root.suspendedLanes; const pingedLanes = root.pingedLanes; diff --git a/packages/react-reconciler/src/ReactFiberReconciler.js b/packages/react-reconciler/src/ReactFiberReconciler.js index 542cfd36b14da..06100010d0ee3 100644 --- a/packages/react-reconciler/src/ReactFiberReconciler.js +++ b/packages/react-reconciler/src/ReactFiberReconciler.js @@ -86,10 +86,6 @@ import { getHighestPriorityPendingLanes, higherPriorityLane, } from './ReactFiberLane'; -import { - getCurrentUpdatePriority, - runWithPriority, -} from './ReactEventPriorities'; import { scheduleRefresh, scheduleRoot, @@ -525,8 +521,6 @@ export function attemptHydrationAtCurrentPriority(fiber: Fiber): void { markRetryLaneIfNotHydrated(fiber, lane); } -export {getCurrentUpdatePriority, runWithPriority}; - export {findHostInstance}; export {findHostInstanceWithWarning}; diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index e4b534fa9c33f..626d5a606fe16 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -71,11 +71,13 @@ import { cancelTimeout, noTimeout, afterActiveInstanceBlur, - getCurrentEventPriority, startSuspendingCommit, waitForCommitToBeReady, preloadInstance, supportsHydration, + setCurrentUpdatePriority, + getCurrentUpdatePriority, + resolveUpdatePriority, } from './ReactFiberConfig'; import {createWorkInProgress, resetWorkInProgress} from './ReactFiber'; @@ -158,10 +160,9 @@ import { import { DiscreteEventPriority, DefaultEventPriority, - getCurrentUpdatePriority, - setCurrentUpdatePriority, lowerEventPriority, lanesToEventPriority, + eventPriorityToLane, } from './ReactEventPriorities'; import {requestCurrentTransition} from './ReactFiberTransition'; import { @@ -642,25 +643,7 @@ export function requestUpdateLane(fiber: Fiber): Lane { requestTransitionLane(transition); } - // Updates originating inside certain React methods, like flushSync, have - // their priority set by tracking it with a context variable. - // - // The opaque type returned by the host config is internally a lane, so we can - // use that directly. - // TODO: Move this type conversion to the event priority module. - const updateLane: Lane = (getCurrentUpdatePriority(): any); - if (updateLane !== NoLane) { - return updateLane; - } - - // This update originated outside React. Ask the host environment for an - // appropriate priority, based on the type of event. - // - // The opaque type returned by the host config is internally a lane, so we can - // use that directly. - // TODO: Move this type conversion to the event priority module. - const eventLane: Lane = (getCurrentEventPriority(): any); - return eventLane; + return eventPriorityToLane(resolveUpdatePriority()); } function requestRetryLane(fiber: Fiber) { @@ -1447,12 +1430,12 @@ export function getExecutionContext(): ExecutionContext { } export function deferredUpdates(fn: () => A): A { - const previousPriority = getCurrentUpdatePriority(); const prevTransition = ReactCurrentBatchConfig.transition; + const previousPriority = getCurrentUpdatePriority(); try { - ReactCurrentBatchConfig.transition = null; setCurrentUpdatePriority(DefaultEventPriority); + ReactCurrentBatchConfig.transition = null; return fn(); } finally { setCurrentUpdatePriority(previousPriority); @@ -1493,11 +1476,11 @@ export function discreteUpdates( c: C, d: D, ): R { - const previousPriority = getCurrentUpdatePriority(); const prevTransition = ReactCurrentBatchConfig.transition; + const previousPriority = getCurrentUpdatePriority(); try { - ReactCurrentBatchConfig.transition = null; setCurrentUpdatePriority(DiscreteEventPriority); + ReactCurrentBatchConfig.transition = null; return fn(a, b, c, d); } finally { setCurrentUpdatePriority(previousPriority); @@ -1534,8 +1517,8 @@ export function flushSync(fn: (() => R) | void): R | void { const previousPriority = getCurrentUpdatePriority(); try { - ReactCurrentBatchConfig.transition = null; setCurrentUpdatePriority(DiscreteEventPriority); + ReactCurrentBatchConfig.transition = null; if (fn) { return fn(); } else { @@ -2716,12 +2699,12 @@ function commitRoot( ) { // TODO: This no longer makes any sense. We already wrap the mutation and // layout phases. Should be able to remove. - const previousUpdateLanePriority = getCurrentUpdatePriority(); const prevTransition = ReactCurrentBatchConfig.transition; + const previousUpdateLanePriority = getCurrentUpdatePriority(); try { - ReactCurrentBatchConfig.transition = null; setCurrentUpdatePriority(DiscreteEventPriority); + ReactCurrentBatchConfig.transition = null; commitRootImpl( root, recoverableErrors, @@ -3190,8 +3173,8 @@ export function flushPassiveEffects(): boolean { const previousPriority = getCurrentUpdatePriority(); try { - ReactCurrentBatchConfig.transition = null; setCurrentUpdatePriority(priority); + ReactCurrentBatchConfig.transition = null; return flushPassiveEffectsImpl(); } finally { setCurrentUpdatePriority(previousPriority); @@ -3546,7 +3529,7 @@ function retryTimedOutBoundary(boundaryFiber: Fiber, retryLane: Lane) { export function retryDehydratedSuspenseBoundary(boundaryFiber: Fiber) { const suspenseState: null | SuspenseState = boundaryFiber.memoizedState; - let retryLane = NoLane; + let retryLane: Lane = NoLane; if (suspenseState !== null) { retryLane = suspenseState.retryLane; } @@ -3554,7 +3537,7 @@ export function retryDehydratedSuspenseBoundary(boundaryFiber: Fiber) { } export function resolveRetryWakeable(boundaryFiber: Fiber, wakeable: Wakeable) { - let retryLane = NoLane; // Default + let retryLane: Lane = NoLane; // Default let retryCache: WeakSet | Set | null; switch (boundaryFiber.tag) { case SuspenseComponent: diff --git a/packages/react-reconciler/src/ReactReconcilerConstants.js b/packages/react-reconciler/src/ReactReconcilerConstants.js index 3e9b1c7009da9..7c650048ddce7 100644 --- a/packages/react-reconciler/src/ReactReconcilerConstants.js +++ b/packages/react-reconciler/src/ReactReconcilerConstants.js @@ -11,6 +11,7 @@ // Only expose the minimal subset necessary to implement a host config. export { + NoEventPriority, DiscreteEventPriority, ContinuousEventPriority, DefaultEventPriority, diff --git a/packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.internal.js b/packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.internal.js index 0a38182ecbe54..753c2d849b19a 100644 --- a/packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.internal.js @@ -15,6 +15,7 @@ let act; let ReactFiberReconciler; let ConcurrentRoot; let DefaultEventPriority; +let NoEventPriority; describe('ReactFiberHostContext', () => { beforeEach(() => { @@ -26,6 +27,8 @@ describe('ReactFiberHostContext', () => { require('react-reconciler/src/ReactRootTags').ConcurrentRoot; DefaultEventPriority = require('react-reconciler/src/ReactEventPriorities').DefaultEventPriority; + NoEventPriority = + require('react-reconciler/src/ReactEventPriorities').NoEventPriority; }); global.IS_REACT_ACT_ENVIRONMENT = true; @@ -34,6 +37,7 @@ describe('ReactFiberHostContext', () => { it('should send the context to prepareForCommit and resetAfterCommit', () => { const rootContext = {}; const childContext = {}; + let updatePriority: typeof DefaultEventPriority = NoEventPriority; const Renderer = ReactFiberReconciler({ prepareForCommit: function (hostContext) { expect(hostContext).toBe(rootContext); @@ -67,7 +71,16 @@ describe('ReactFiberHostContext', () => { return null; }, clearContainer: function () {}, - getCurrentEventPriority: function () { + setCurrentUpdatePriority: function (newPriority: any) { + updatePriority = newPriority; + }, + getCurrentUpdatePriority: function () { + return updatePriority; + }, + resolveUpdatePriority: function () { + if (updatePriority !== NoEventPriority) { + return updatePriority; + } return DefaultEventPriority; }, shouldAttemptEagerTransition() { diff --git a/packages/react-reconciler/src/forks/ReactFiberConfig.custom.js b/packages/react-reconciler/src/forks/ReactFiberConfig.custom.js index 492b1a4a8906f..348a70564c1b7 100644 --- a/packages/react-reconciler/src/forks/ReactFiberConfig.custom.js +++ b/packages/react-reconciler/src/forks/ReactFiberConfig.custom.js @@ -65,7 +65,9 @@ export const afterActiveInstanceBlur = $$$config.afterActiveInstanceBlur; export const preparePortalMount = $$$config.preparePortalMount; export const prepareScopeUpdate = $$$config.prepareScopeUpdate; export const getInstanceFromScope = $$$config.getInstanceFromScope; -export const getCurrentEventPriority = $$$config.getCurrentEventPriority; +export const setCurrentUpdatePriority = $$$config.setCurrentUpdatePriority; +export const getCurrentUpdatePriority = $$$config.getCurrentUpdatePriority; +export const resolveUpdatePriority = $$$config.resolveUpdatePriority; export const shouldAttemptEagerTransition = $$$config.shouldAttemptEagerTransition; export const detachDeletedInstance = $$$config.detachDeletedInstance; diff --git a/packages/react-test-renderer/src/ReactFiberConfigTestHost.js b/packages/react-test-renderer/src/ReactFiberConfigTestHost.js index e374b25922b99..b20d2054f1002 100644 --- a/packages/react-test-renderer/src/ReactFiberConfigTestHost.js +++ b/packages/react-test-renderer/src/ReactFiberConfigTestHost.js @@ -10,6 +10,7 @@ import isArray from 'shared/isArray'; import { DefaultEventPriority, + NoEventPriority, type EventPriority, } from 'react-reconciler/src/ReactEventPriorities'; @@ -201,7 +202,19 @@ export function createTextInstance( }; } -export function getCurrentEventPriority(): EventPriority { +let currentUpdatePriority: EventPriority = NoEventPriority; +export function setCurrentUpdatePriority(newPriority: EventPriority): void { + currentUpdatePriority = newPriority; +} + +export function getCurrentUpdatePriority(): EventPriority { + return currentUpdatePriority; +} + +export function resolveUpdatePriority(): EventPriority { + if (currentUpdatePriority !== NoEventPriority) { + return currentUpdatePriority; + } return DefaultEventPriority; } export function shouldAttemptEagerTransition(): boolean {