mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[DOM] move flushSync out of the reconciler (#28500)
This PR moves `flushSync` out of the reconciler. there is still an internal implementation that is used when these semantics are needed for React methods such as `unmount` on roots. This new isomorphic `flushSync` is only used in builds that no longer support legacy mode. Additionally all the internal uses of flushSync in the reconciler have been replaced with more direct methods. There is a new `updateContainerSync` method which updates a container but forces it to the Sync lane and flushes passive effects if necessary. This combined with flushSyncWork can be used to replace flushSync for all instances of internal usage. We still maintain the original flushSync implementation as `flushSyncFromReconciler` because it will be used as the flushSync implementation for FB builds. This is because it has special legacy mode handling that the new isomorphic implementation does not need to consider. It will be removed from production OSS builds by closure though
This commit is contained in:
Vendored
+8
-11
@@ -10,9 +10,9 @@ import ReactVersion from 'shared/ReactVersion';
|
||||
import {LegacyRoot, ConcurrentRoot} from 'react-reconciler/src/ReactRootTags';
|
||||
import {
|
||||
createContainer,
|
||||
updateContainer,
|
||||
updateContainerSync,
|
||||
injectIntoDevTools,
|
||||
flushSync,
|
||||
flushSyncWork,
|
||||
} from 'react-reconciler/src/ReactFiberReconciler';
|
||||
import Transform from 'art/core/transform';
|
||||
import Mode from 'art/modes/current';
|
||||
@@ -78,9 +78,8 @@ class Surface extends React.Component {
|
||||
);
|
||||
// We synchronously flush updates coming from above so that they commit together
|
||||
// and so that refs resolve before the parent life cycles.
|
||||
flushSync(() => {
|
||||
updateContainer(this.props.children, this._mountNode, this);
|
||||
});
|
||||
updateContainerSync(this.props.children, this._mountNode, this);
|
||||
flushSyncWork();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
@@ -92,9 +91,8 @@ class Surface extends React.Component {
|
||||
|
||||
// We synchronously flush updates coming from above so that they commit together
|
||||
// and so that refs resolve before the parent life cycles.
|
||||
flushSync(() => {
|
||||
updateContainer(this.props.children, this._mountNode, this);
|
||||
});
|
||||
updateContainerSync(this.props.children, this._mountNode, this);
|
||||
flushSyncWork();
|
||||
|
||||
if (this._surface.render) {
|
||||
this._surface.render();
|
||||
@@ -104,9 +102,8 @@ class Surface extends React.Component {
|
||||
componentWillUnmount() {
|
||||
// We synchronously flush updates coming from above so that they commit together
|
||||
// and so that refs resolve before the parent life cycles.
|
||||
flushSync(() => {
|
||||
updateContainer(null, this._mountNode, this);
|
||||
});
|
||||
updateContainerSync(null, this._mountNode, this);
|
||||
flushSyncWork();
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -90,6 +90,7 @@ import {
|
||||
enableScopeAPI,
|
||||
enableTrustedTypesIntegration,
|
||||
enableAsyncActions,
|
||||
disableLegacyMode,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
HostComponent,
|
||||
@@ -100,6 +101,7 @@ import {
|
||||
import {listenToAllSupportedEvents} from '../events/DOMPluginEventSystem';
|
||||
import {validateLinkPropsForStyleResource} from '../shared/ReactDOMResourceValidation';
|
||||
import escapeSelectorAttributeValueInsideDoubleQuotes from './escapeSelectorAttributeValueInsideDoubleQuotes';
|
||||
import {flushSyncWork as flushSyncWorkOnAllRoots} from 'react-reconciler/src/ReactFiberWorkLoop';
|
||||
|
||||
import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals';
|
||||
const ReactDOMCurrentDispatcher =
|
||||
@@ -1924,6 +1926,9 @@ function getDocumentFromRoot(root: HoistableRoot): Document {
|
||||
|
||||
const previousDispatcher = ReactDOMCurrentDispatcher.current;
|
||||
ReactDOMCurrentDispatcher.current = {
|
||||
flushSyncWork: disableLegacyMode
|
||||
? flushSyncWork
|
||||
: previousDispatcher.flushSyncWork,
|
||||
prefetchDNS,
|
||||
preconnect,
|
||||
preload,
|
||||
@@ -1933,6 +1938,20 @@ ReactDOMCurrentDispatcher.current = {
|
||||
preinitModuleScript,
|
||||
};
|
||||
|
||||
function flushSyncWork() {
|
||||
if (disableLegacyMode) {
|
||||
const previousWasRendering = previousDispatcher.flushSyncWork();
|
||||
const wasRendering = flushSyncWorkOnAllRoots();
|
||||
// Since multiple dispatchers can flush sync work during a single flushSync call
|
||||
// we need to return true if any of them were rendering.
|
||||
return previousWasRendering || wasRendering;
|
||||
} else {
|
||||
throw new Error(
|
||||
'flushSyncWork should not be called from builds that support legacy mode. This is a bug in React.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// We expect this to get inlined. It is a function mostly to communicate the special nature of
|
||||
// how we resolve the HoistableRoot for ReactDOM.pre*() methods. Because we support calling
|
||||
// these methods outside of render there is no way to know which Document or ShadowRoot is 'scoped'
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
import {
|
||||
batchedUpdates as batchedUpdatesImpl,
|
||||
discreteUpdates as discreteUpdatesImpl,
|
||||
flushSync as flushSyncImpl,
|
||||
flushSyncWork,
|
||||
} from 'react-reconciler/src/ReactFiberReconciler';
|
||||
|
||||
// Used as a way to call batchedUpdates when we don't have a reference to
|
||||
@@ -36,7 +36,9 @@ function finishEventHandler() {
|
||||
// bails out of the update without touching the DOM.
|
||||
// TODO: Restore state in the microtask, after the discrete updates flush,
|
||||
// instead of early flushing them here.
|
||||
flushSyncImpl();
|
||||
// @TODO Should move to flushSyncWork once legacy mode is removed but since this flushSync
|
||||
// flushes passive effects we can't do this yet.
|
||||
flushSyncWork();
|
||||
restoreStateIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ const ReactDOMCurrentDispatcher =
|
||||
|
||||
const previousDispatcher = ReactDOMCurrentDispatcher.current;
|
||||
ReactDOMCurrentDispatcher.current = {
|
||||
flushSyncWork: previousDispatcher.flushSyncWork,
|
||||
prefetchDNS,
|
||||
preconnect,
|
||||
preload,
|
||||
|
||||
@@ -88,6 +88,7 @@ const ReactDOMCurrentDispatcher =
|
||||
|
||||
const previousDispatcher = ReactDOMCurrentDispatcher.current;
|
||||
ReactDOMCurrentDispatcher.current = {
|
||||
flushSyncWork: previousDispatcher.flushSyncWork,
|
||||
prefetchDNS,
|
||||
preconnect,
|
||||
preload,
|
||||
|
||||
@@ -29,6 +29,7 @@ type InternalsType = {
|
||||
function noop() {}
|
||||
|
||||
const DefaultDispatcher: HostDispatcher = {
|
||||
flushSyncWork: noop,
|
||||
prefetchDNS: noop,
|
||||
preconnect: noop,
|
||||
preload: noop,
|
||||
|
||||
+10
-4
@@ -14,6 +14,7 @@ import type {
|
||||
CreateRootOptions,
|
||||
} from './ReactDOMRoot';
|
||||
|
||||
import {disableLegacyMode} from 'shared/ReactFeatureFlags';
|
||||
import {
|
||||
createRoot as createRootImpl,
|
||||
hydrateRoot as hydrateRootImpl,
|
||||
@@ -21,9 +22,10 @@ import {
|
||||
} from './ReactDOMRoot';
|
||||
import {createEventHandle} from 'react-dom-bindings/src/client/ReactDOMEventHandle';
|
||||
import {runWithPriority} from 'react-dom-bindings/src/client/ReactDOMUpdatePriority';
|
||||
import {flushSync as flushSyncIsomorphic} from '../shared/ReactDOMFlushSync';
|
||||
|
||||
import {
|
||||
flushSync as flushSyncWithoutWarningIfAlreadyRendering,
|
||||
flushSyncFromReconciler as flushSyncWithoutWarningIfAlreadyRendering,
|
||||
isAlreadyRendering,
|
||||
injectIntoDevTools,
|
||||
findHostInstance,
|
||||
@@ -123,11 +125,11 @@ function hydrateRoot(
|
||||
|
||||
// Overload the definition to the two valid signatures.
|
||||
// Warning, this opts-out of checking the function body.
|
||||
declare function flushSync<R>(fn: () => R): R;
|
||||
declare function flushSyncFromReconciler<R>(fn: () => R): R;
|
||||
// eslint-disable-next-line no-redeclare
|
||||
declare function flushSync(): void;
|
||||
declare function flushSyncFromReconciler(): void;
|
||||
// eslint-disable-next-line no-redeclare
|
||||
function flushSync<R>(fn: (() => R) | void): R | void {
|
||||
function flushSyncFromReconciler<R>(fn: (() => R) | void): R | void {
|
||||
if (__DEV__) {
|
||||
if (isAlreadyRendering()) {
|
||||
console.error(
|
||||
@@ -140,6 +142,10 @@ function flushSync<R>(fn: (() => R) | void): R | void {
|
||||
return flushSyncWithoutWarningIfAlreadyRendering(fn);
|
||||
}
|
||||
|
||||
const flushSync: typeof flushSyncIsomorphic = disableLegacyMode
|
||||
? flushSyncIsomorphic
|
||||
: flushSyncFromReconciler;
|
||||
|
||||
function findDOMNode(
|
||||
componentOrElement: React$Component<any, any>,
|
||||
): null | Element | Text {
|
||||
|
||||
+4
-4
@@ -93,7 +93,8 @@ import {
|
||||
createContainer,
|
||||
createHydrationContainer,
|
||||
updateContainer,
|
||||
flushSync,
|
||||
updateContainerSync,
|
||||
flushSyncWork,
|
||||
isAlreadyRendering,
|
||||
defaultOnUncaughtError,
|
||||
defaultOnCaughtError,
|
||||
@@ -161,9 +162,8 @@ ReactDOMHydrationRoot.prototype.unmount = ReactDOMRoot.prototype.unmount =
|
||||
);
|
||||
}
|
||||
}
|
||||
flushSync(() => {
|
||||
updateContainer(null, root, null, null);
|
||||
});
|
||||
updateContainerSync(null, root, null, null);
|
||||
flushSyncWork();
|
||||
unmarkContainerAsRoot(container);
|
||||
}
|
||||
};
|
||||
|
||||
+12
-15
@@ -49,7 +49,8 @@ import {
|
||||
createHydrationContainer,
|
||||
findHostInstanceWithNoPortals,
|
||||
updateContainer,
|
||||
flushSync,
|
||||
updateContainerSync,
|
||||
flushSyncWork,
|
||||
getPublicRootInstance,
|
||||
findHostInstance,
|
||||
findHostInstanceWithWarning,
|
||||
@@ -247,7 +248,7 @@ function legacyCreateRootFromDOMContainer(
|
||||
// $FlowFixMe[incompatible-call]
|
||||
listenToAllSupportedEvents(rootContainerElement);
|
||||
|
||||
flushSync();
|
||||
flushSyncWork();
|
||||
return root;
|
||||
} else {
|
||||
// First clear any existing content.
|
||||
@@ -282,9 +283,8 @@ function legacyCreateRootFromDOMContainer(
|
||||
listenToAllSupportedEvents(rootContainerElement);
|
||||
|
||||
// Initial mount should not be batched.
|
||||
flushSync(() => {
|
||||
updateContainer(initialChildren, root, parentComponent, callback);
|
||||
});
|
||||
updateContainerSync(initialChildren, root, parentComponent, callback);
|
||||
flushSyncWork();
|
||||
|
||||
return root;
|
||||
}
|
||||
@@ -485,6 +485,8 @@ export function unmountComponentAtNode(container: Container): boolean {
|
||||
}
|
||||
|
||||
if (container._reactRootContainer) {
|
||||
const root = container._reactRootContainer;
|
||||
|
||||
if (__DEV__) {
|
||||
const rootEl = getReactRootElementInContainer(container);
|
||||
const renderedByDifferentReact = rootEl && !getInstanceFromNode(rootEl);
|
||||
@@ -496,16 +498,11 @@ export function unmountComponentAtNode(container: Container): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
// Unmount should not be batched.
|
||||
flushSync(() => {
|
||||
legacyRenderSubtreeIntoContainer(null, null, container, false, () => {
|
||||
// $FlowFixMe[incompatible-type] This should probably use `delete container._reactRootContainer`
|
||||
container._reactRootContainer = null;
|
||||
unmarkContainerAsRoot(container);
|
||||
});
|
||||
});
|
||||
// If you call unmountComponentAtNode twice in quick succession, you'll
|
||||
// get `true` twice. That's probably fine?
|
||||
updateContainerSync(null, root, null, null);
|
||||
flushSyncWork();
|
||||
// $FlowFixMe[incompatible-type] This should probably use `delete container._reactRootContainer`
|
||||
container._reactRootContainer = null;
|
||||
unmarkContainerAsRoot(container);
|
||||
return true;
|
||||
} else {
|
||||
if (__DEV__) {
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* 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 {BatchConfig} from 'react/src/ReactCurrentBatchConfig';
|
||||
|
||||
import {disableLegacyMode} from 'shared/ReactFeatureFlags';
|
||||
import {DiscreteEventPriority} from 'react-reconciler/src/ReactEventPriorities';
|
||||
|
||||
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||
const ReactCurrentBatchConfig: BatchConfig =
|
||||
ReactSharedInternals.ReactCurrentBatchConfig;
|
||||
|
||||
import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals';
|
||||
const ReactDOMCurrentDispatcher =
|
||||
ReactDOMSharedInternals.ReactDOMCurrentDispatcher;
|
||||
|
||||
declare function flushSyncImpl<R>(fn: () => R): R;
|
||||
declare function flushSyncImpl(void): void;
|
||||
function flushSyncImpl<R>(fn: (() => R) | void): R | void {
|
||||
const previousTransition = ReactCurrentBatchConfig.transition;
|
||||
const previousUpdatePriority =
|
||||
ReactDOMSharedInternals.up; /* ReactDOMCurrentUpdatePriority */
|
||||
|
||||
try {
|
||||
ReactCurrentBatchConfig.transition = null;
|
||||
ReactDOMSharedInternals.up /* ReactDOMCurrentUpdatePriority */ =
|
||||
DiscreteEventPriority;
|
||||
if (fn) {
|
||||
return fn();
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
} finally {
|
||||
ReactCurrentBatchConfig.transition = previousTransition;
|
||||
ReactDOMSharedInternals.up /* ReactDOMCurrentUpdatePriority */ =
|
||||
previousUpdatePriority;
|
||||
const wasInRender = ReactDOMCurrentDispatcher.current.flushSyncWork();
|
||||
if (__DEV__) {
|
||||
if (wasInRender) {
|
||||
console.error(
|
||||
'flushSync was called from inside a lifecycle method. React cannot ' +
|
||||
'flush when React is already rendering. Consider moving this call to ' +
|
||||
'a scheduler task or micro task.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare function flushSyncErrorInBuildsThatSupportLegacyMode<R>(fn: () => R): R;
|
||||
declare function flushSyncErrorInBuildsThatSupportLegacyMode(void): void;
|
||||
function flushSyncErrorInBuildsThatSupportLegacyMode() {
|
||||
// eslint-disable-next-line react-internal/prod-error-codes
|
||||
throw new Error(
|
||||
'Expected this build of React to not support legacy mode but it does. This is a bug in React.',
|
||||
);
|
||||
}
|
||||
|
||||
export const flushSync: typeof flushSyncImpl = disableLegacyMode
|
||||
? flushSyncImpl
|
||||
: flushSyncErrorInBuildsThatSupportLegacyMode;
|
||||
@@ -82,6 +82,7 @@ export type PreinitModuleScriptOptions = {
|
||||
};
|
||||
|
||||
export type HostDispatcher = {
|
||||
flushSyncWork: () => boolean | void,
|
||||
prefetchDNS: (href: string) => void,
|
||||
preconnect: (href: string, crossOrigin?: ?CrossOriginEnum) => void,
|
||||
preload: (href: string, as: string, options?: ?PreloadImplOptions) => void,
|
||||
|
||||
+24
-1
@@ -29,6 +29,7 @@ import isArray from 'shared/isArray';
|
||||
import {checkPropStringCoercion} from 'shared/CheckStringCoercion';
|
||||
import {
|
||||
NoEventPriority,
|
||||
DiscreteEventPriority,
|
||||
DefaultEventPriority,
|
||||
IdleEventPriority,
|
||||
ConcurrentRoot,
|
||||
@@ -40,6 +41,9 @@ import {
|
||||
disableStringRefs,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
|
||||
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||
const ReactCurrentBatchConfig = ReactSharedInternals.ReactCurrentBatchConfig;
|
||||
|
||||
type Container = {
|
||||
rootID: string,
|
||||
children: Array<Instance | TextInstance>,
|
||||
@@ -943,7 +947,25 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
|
||||
);
|
||||
}
|
||||
}
|
||||
return NoopRenderer.flushSync(fn);
|
||||
if (disableLegacyMode) {
|
||||
const previousTransition = ReactCurrentBatchConfig.transition;
|
||||
const preivousEventPriority = currentEventPriority;
|
||||
try {
|
||||
ReactCurrentBatchConfig.transition = null;
|
||||
currentEventPriority = DiscreteEventPriority;
|
||||
if (fn) {
|
||||
return fn();
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
} finally {
|
||||
ReactCurrentBatchConfig.transition = previousTransition;
|
||||
currentEventPriority = preivousEventPriority;
|
||||
NoopRenderer.flushSyncWork();
|
||||
}
|
||||
} else {
|
||||
return NoopRenderer.flushSyncFromReconciler(fn);
|
||||
}
|
||||
}
|
||||
|
||||
function onRecoverableError(error) {
|
||||
@@ -1081,6 +1103,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
|
||||
getChildrenAsJSX() {
|
||||
return getChildrenAsJSX(container);
|
||||
},
|
||||
legacy: true,
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
+10
-13
@@ -15,12 +15,12 @@ import type {Instance} from './ReactFiberConfig';
|
||||
import type {ReactNodeList} from 'shared/ReactTypes';
|
||||
|
||||
import {
|
||||
flushSync,
|
||||
flushSyncWork,
|
||||
scheduleUpdateOnFiber,
|
||||
flushPassiveEffects,
|
||||
} from './ReactFiberWorkLoop';
|
||||
import {enqueueConcurrentRenderForLane} from './ReactFiberConcurrentUpdates';
|
||||
import {updateContainer} from './ReactFiberReconciler';
|
||||
import {updateContainerSync} from './ReactFiberReconciler';
|
||||
import {emptyContextObject} from './ReactFiberContext';
|
||||
import {SyncLane} from './ReactFiberLane';
|
||||
import {
|
||||
@@ -241,13 +241,12 @@ export const scheduleRefresh: ScheduleRefresh = (
|
||||
}
|
||||
const {staleFamilies, updatedFamilies} = update;
|
||||
flushPassiveEffects();
|
||||
flushSync(() => {
|
||||
scheduleFibersWithFamiliesRecursively(
|
||||
root.current,
|
||||
updatedFamilies,
|
||||
staleFamilies,
|
||||
);
|
||||
});
|
||||
scheduleFibersWithFamiliesRecursively(
|
||||
root.current,
|
||||
updatedFamilies,
|
||||
staleFamilies,
|
||||
);
|
||||
flushSyncWork();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -262,10 +261,8 @@ export const scheduleRoot: ScheduleRoot = (
|
||||
// Just ignore. We'll delete this with _renderSubtree code path later.
|
||||
return;
|
||||
}
|
||||
flushPassiveEffects();
|
||||
flushSync(() => {
|
||||
updateContainer(element, root, null, null);
|
||||
});
|
||||
updateContainerSync(element, root, null, null);
|
||||
flushSyncWork();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+55
-15
@@ -25,6 +25,7 @@ import type {ReactNodeList, ReactFormState} from 'shared/ReactTypes';
|
||||
import type {Lane} from './ReactFiberLane';
|
||||
import type {SuspenseState} from './ReactFiberSuspenseComponent';
|
||||
|
||||
import {LegacyRoot} from './ReactRootTags';
|
||||
import {
|
||||
findCurrentHostFiber,
|
||||
findCurrentHostFiberWithNoPortals,
|
||||
@@ -61,7 +62,8 @@ import {
|
||||
scheduleInitialHydrationOnRoot,
|
||||
flushRoot,
|
||||
batchedUpdates,
|
||||
flushSync,
|
||||
flushSyncFromReconciler,
|
||||
flushSyncWork,
|
||||
isAlreadyRendering,
|
||||
deferredUpdates,
|
||||
discreteUpdates,
|
||||
@@ -357,11 +359,51 @@ export function updateContainer(
|
||||
parentComponent: ?React$Component<any, any>,
|
||||
callback: ?Function,
|
||||
): Lane {
|
||||
const current = container.current;
|
||||
const lane = requestUpdateLane(current);
|
||||
updateContainerImpl(
|
||||
current,
|
||||
lane,
|
||||
element,
|
||||
container,
|
||||
parentComponent,
|
||||
callback,
|
||||
);
|
||||
return lane;
|
||||
}
|
||||
|
||||
export function updateContainerSync(
|
||||
element: ReactNodeList,
|
||||
container: OpaqueRoot,
|
||||
parentComponent: ?React$Component<any, any>,
|
||||
callback: ?Function,
|
||||
): Lane {
|
||||
if (container.tag === LegacyRoot) {
|
||||
flushPassiveEffects();
|
||||
}
|
||||
const current = container.current;
|
||||
updateContainerImpl(
|
||||
current,
|
||||
SyncLane,
|
||||
element,
|
||||
container,
|
||||
parentComponent,
|
||||
callback,
|
||||
);
|
||||
return SyncLane;
|
||||
}
|
||||
|
||||
function updateContainerImpl(
|
||||
rootFiber: Fiber,
|
||||
lane: Lane,
|
||||
element: ReactNodeList,
|
||||
container: OpaqueRoot,
|
||||
parentComponent: ?React$Component<any, any>,
|
||||
callback: ?Function,
|
||||
): void {
|
||||
if (__DEV__) {
|
||||
onScheduleRoot(container, element);
|
||||
}
|
||||
const current = container.current;
|
||||
const lane = requestUpdateLane(current);
|
||||
|
||||
if (enableSchedulingProfiler) {
|
||||
markRenderScheduled(lane);
|
||||
@@ -410,20 +452,19 @@ export function updateContainer(
|
||||
update.callback = callback;
|
||||
}
|
||||
|
||||
const root = enqueueUpdate(current, update, lane);
|
||||
const root = enqueueUpdate(rootFiber, update, lane);
|
||||
if (root !== null) {
|
||||
scheduleUpdateOnFiber(root, current, lane);
|
||||
entangleTransitions(root, current, lane);
|
||||
scheduleUpdateOnFiber(root, rootFiber, lane);
|
||||
entangleTransitions(root, rootFiber, lane);
|
||||
}
|
||||
|
||||
return lane;
|
||||
}
|
||||
|
||||
export {
|
||||
batchedUpdates,
|
||||
deferredUpdates,
|
||||
discreteUpdates,
|
||||
flushSync,
|
||||
flushSyncFromReconciler,
|
||||
flushSyncWork,
|
||||
isAlreadyRendering,
|
||||
flushPassiveEffects,
|
||||
};
|
||||
@@ -456,12 +497,11 @@ export function attemptSynchronousHydration(fiber: Fiber): void {
|
||||
break;
|
||||
}
|
||||
case SuspenseComponent: {
|
||||
flushSync(() => {
|
||||
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
|
||||
if (root !== null) {
|
||||
scheduleUpdateOnFiber(root, fiber, SyncLane);
|
||||
}
|
||||
});
|
||||
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
|
||||
if (root !== null) {
|
||||
scheduleUpdateOnFiber(root, fiber, SyncLane);
|
||||
}
|
||||
flushSyncWork();
|
||||
// If we're still blocked after this, we need to increase
|
||||
// the priority of any promises resolving within this
|
||||
// boundary so that they next attempt also has higher pri.
|
||||
|
||||
+25
-14
@@ -9,6 +9,7 @@
|
||||
|
||||
import {REACT_STRICT_MODE_TYPE} from 'shared/ReactSymbols';
|
||||
|
||||
import type {BatchConfig} from 'react/src/ReactCurrentBatchConfig';
|
||||
import type {Wakeable, Thenable} from 'shared/ReactTypes';
|
||||
import type {Fiber, FiberRoot} from './ReactInternalTypes';
|
||||
import type {Lanes, Lane} from './ReactFiberLane';
|
||||
@@ -281,13 +282,12 @@ import {logUncaughtError} from './ReactFiberErrorLogger';
|
||||
|
||||
const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
|
||||
|
||||
const {
|
||||
ReactCurrentDispatcher,
|
||||
ReactCurrentCache,
|
||||
ReactCurrentOwner,
|
||||
ReactCurrentBatchConfig,
|
||||
ReactCurrentActQueue,
|
||||
} = ReactSharedInternals;
|
||||
const ReactCurrentDispatcher = ReactSharedInternals.ReactCurrentDispatcher;
|
||||
const ReactCurrentCache = ReactSharedInternals.ReactCurrentCache;
|
||||
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
|
||||
const ReactCurrentBatchConfig: BatchConfig =
|
||||
ReactSharedInternals.ReactCurrentBatchConfig;
|
||||
const ReactCurrentActQueue = ReactSharedInternals.ReactCurrentActQueue;
|
||||
|
||||
type ExecutionContext = number;
|
||||
|
||||
@@ -625,12 +625,11 @@ export function requestUpdateLane(fiber: Fiber): Lane {
|
||||
const transition = requestCurrentTransition();
|
||||
if (transition !== null) {
|
||||
if (__DEV__) {
|
||||
const batchConfigTransition = ReactCurrentBatchConfig.transition;
|
||||
if (!batchConfigTransition._updatedFibers) {
|
||||
batchConfigTransition._updatedFibers = new Set();
|
||||
if (!transition._updatedFibers) {
|
||||
transition._updatedFibers = new Set();
|
||||
}
|
||||
|
||||
batchConfigTransition._updatedFibers.add(fiber);
|
||||
transition._updatedFibers.add(fiber);
|
||||
}
|
||||
|
||||
const actionScopeLane = peekEntangledActionLane();
|
||||
@@ -776,6 +775,8 @@ export function scheduleUpdateOnFiber(
|
||||
transition.startTime = now();
|
||||
}
|
||||
|
||||
// $FlowFixMe[prop-missing]: The BatchConfigTransition and Transition types are incompatible but was previously untyped and thus uncaught
|
||||
// $FlowFixMe[incompatible-call]: "
|
||||
addTransitionToLanesMap(root, transition, lane);
|
||||
}
|
||||
}
|
||||
@@ -1494,11 +1495,11 @@ export function discreteUpdates<A, B, C, D, R>(
|
||||
// Overload the definition to the two valid signatures.
|
||||
// Warning, this opts-out of checking the function body.
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
declare function flushSync<R>(fn: () => R): R;
|
||||
declare function flushSyncFromReconciler<R>(fn: () => R): R;
|
||||
// eslint-disable-next-line no-redeclare
|
||||
declare function flushSync(void): void;
|
||||
declare function flushSyncFromReconciler(void): void;
|
||||
// eslint-disable-next-line no-redeclare
|
||||
export function flushSync<R>(fn: (() => R) | void): R | void {
|
||||
export function flushSyncFromReconciler<R>(fn: (() => R) | void): R | void {
|
||||
// In legacy mode, we flush pending passive effects at the beginning of the
|
||||
// next event, not at the end of the previous one.
|
||||
if (
|
||||
@@ -1538,6 +1539,16 @@ export function flushSync<R>(fn: (() => R) | void): R | void {
|
||||
}
|
||||
}
|
||||
|
||||
// If called outside of a render or commit will flush all sync work on all roots
|
||||
// Returns whether the the call was during a render or not
|
||||
export function flushSyncWork(): boolean {
|
||||
if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
|
||||
flushSyncWorkOnAllRoots();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function isAlreadyRendering(): boolean {
|
||||
// Used by the renderer to print a warning if certain APIs are called from
|
||||
// the wrong context.
|
||||
|
||||
+3
-3
@@ -20,7 +20,7 @@ import {
|
||||
getPublicRootInstance,
|
||||
createContainer,
|
||||
updateContainer,
|
||||
flushSync,
|
||||
flushSyncFromReconciler,
|
||||
injectIntoDevTools,
|
||||
batchedUpdates,
|
||||
defaultOnUncaughtError,
|
||||
@@ -468,7 +468,7 @@ function create(
|
||||
update(newElement: React$Element<any>): any,
|
||||
unmount(): void,
|
||||
getInstance(): React$Component<any, any> | PublicInstance | null,
|
||||
unstable_flushSync: typeof flushSync,
|
||||
unstable_flushSync: typeof flushSyncFromReconciler,
|
||||
} {
|
||||
if (__DEV__) {
|
||||
if (
|
||||
@@ -597,7 +597,7 @@ function create(
|
||||
return getPublicRootInstance(root);
|
||||
},
|
||||
|
||||
unstable_flushSync: flushSync,
|
||||
unstable_flushSync: flushSyncFromReconciler,
|
||||
};
|
||||
|
||||
Object.defineProperty(
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
import type {BatchConfigTransition} from 'react-reconciler/src/ReactFiberTracingMarkerComponent';
|
||||
|
||||
type BatchConfig = {
|
||||
export type BatchConfig = {
|
||||
transition: BatchConfigTransition | null,
|
||||
};
|
||||
/**
|
||||
|
||||
@@ -505,5 +505,6 @@
|
||||
"517": "Symbols cannot be passed to a Server Function without a temporary reference set. Pass a TemporaryReferenceSet to the options.%s",
|
||||
"518": "Saw multiple hydration diff roots in a pass. This is a bug in React.",
|
||||
"519": "Hydration Mismatch Exception: This is not a real error, and should not leak into userspace. If you're seeing this, it's likely a bug in React.",
|
||||
"520": "There was an error during concurrent rendering but React was able to recover by instead synchronously rendering the entire root."
|
||||
"520": "There was an error during concurrent rendering but React was able to recover by instead synchronously rendering the entire root.",
|
||||
"521": "flushSyncWork should not be called from builds that support legacy mode. This is a bug in React."
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user