mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
If an event in the old system is dispatched synchronously within an event from the new system, or vice versa, and the inner event is a discrete update, React should not flush pending discrete updates before firing the inner event's handlers, even if the outer event is not discrete. Another way of saying this is that nested events should never force React to flush discrete updates. Arguably, if the outer event is not a discrete event, then the inner event _should_ flush the pending events. However, that would be a breaking change. I would argue this isn't so bad, however, given that nested events are pretty rare. They don't fit nicely into our event model regardless, since we don't support nested React renders. In the future we should consider warning when events are nested.
125 lines
4.2 KiB
JavaScript
125 lines
4.2 KiB
JavaScript
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
import {
|
|
needsStateRestore,
|
|
restoreStateIfNeeded,
|
|
} from './ReactControlledComponent';
|
|
import {enableEventAPI} from 'shared/ReactFeatureFlags';
|
|
|
|
// Used as a way to call batchedUpdates when we don't have a reference to
|
|
// the renderer. Such as when we're dispatching events or if third party
|
|
// libraries need to call batchedUpdates. Eventually, this API will go away when
|
|
// everything is batched by default. We'll then have a similar API to opt-out of
|
|
// scheduled work and instead do synchronous work.
|
|
|
|
// Defaults
|
|
let batchedUpdatesImpl = function(fn, bookkeeping) {
|
|
return fn(bookkeeping);
|
|
};
|
|
let discreteUpdatesImpl = function(fn, a, b, c) {
|
|
return fn(a, b, c);
|
|
};
|
|
let flushDiscreteUpdatesImpl = function() {};
|
|
let batchedEventUpdatesImpl = batchedUpdatesImpl;
|
|
|
|
let isInsideEventHandler = false;
|
|
|
|
function finishEventHandler() {
|
|
// Here we wait until all updates have propagated, which is important
|
|
// when using controlled components within layers:
|
|
// https://github.com/facebook/react/issues/1698
|
|
// Then we restore state of any controlled component.
|
|
const controlledComponentsHavePendingUpdates = needsStateRestore();
|
|
if (controlledComponentsHavePendingUpdates) {
|
|
// If a controlled event was fired, we may need to restore the state of
|
|
// the DOM node back to the controlled value. This is necessary when React
|
|
// bails out of the update without touching the DOM.
|
|
flushDiscreteUpdatesImpl();
|
|
restoreStateIfNeeded();
|
|
}
|
|
}
|
|
|
|
export function batchedUpdates(fn, bookkeeping) {
|
|
if (isInsideEventHandler) {
|
|
// If we are currently inside another batch, we need to wait until it
|
|
// fully completes before restoring state.
|
|
return fn(bookkeeping);
|
|
}
|
|
isInsideEventHandler = true;
|
|
try {
|
|
return batchedUpdatesImpl(fn, bookkeeping);
|
|
} finally {
|
|
isInsideEventHandler = false;
|
|
finishEventHandler();
|
|
}
|
|
}
|
|
|
|
export function batchedEventUpdates(fn, bookkeeping) {
|
|
if (isInsideEventHandler) {
|
|
// If we are currently inside another batch, we need to wait until it
|
|
// fully completes before restoring state.
|
|
return fn(bookkeeping);
|
|
}
|
|
isInsideEventHandler = true;
|
|
try {
|
|
return batchedEventUpdatesImpl(fn, bookkeeping);
|
|
} finally {
|
|
isInsideEventHandler = false;
|
|
finishEventHandler();
|
|
}
|
|
}
|
|
|
|
export function discreteUpdates(fn, a, b, c) {
|
|
const prevIsInsideEventHandler = isInsideEventHandler;
|
|
isInsideEventHandler = true;
|
|
try {
|
|
return discreteUpdatesImpl(fn, a, b, c);
|
|
} finally {
|
|
isInsideEventHandler = prevIsInsideEventHandler;
|
|
if (!isInsideEventHandler) {
|
|
finishEventHandler();
|
|
}
|
|
}
|
|
}
|
|
|
|
let lastFlushedEventTimeStamp = 0;
|
|
export function flushDiscreteUpdatesIfNeeded(timeStamp: number) {
|
|
// event.timeStamp isn't overly reliable due to inconsistencies in
|
|
// how different browsers have historically provided the time stamp.
|
|
// Some browsers provide high-resolution time stamps for all events,
|
|
// some provide low-resoltion time stamps for all events. FF < 52
|
|
// even mixes both time stamps together. Some browsers even report
|
|
// negative time stamps or time stamps that are 0 (iOS9) in some cases.
|
|
// Given we are only comparing two time stamps with equality (!==),
|
|
// we are safe from the resolution differences. If the time stamp is 0
|
|
// we bail-out of preventing the flush, which can affect semantics,
|
|
// such as if an earlier flush removes or adds event listeners that
|
|
// are fired in the subsequent flush. However, this is the same
|
|
// behaviour as we had before this change, so the risks are low.
|
|
if (
|
|
!isInsideEventHandler &&
|
|
(!enableEventAPI ||
|
|
(timeStamp === 0 || lastFlushedEventTimeStamp !== timeStamp))
|
|
) {
|
|
lastFlushedEventTimeStamp = timeStamp;
|
|
flushDiscreteUpdatesImpl();
|
|
}
|
|
}
|
|
|
|
export function setBatchingImplementation(
|
|
_batchedUpdatesImpl,
|
|
_discreteUpdatesImpl,
|
|
_flushDiscreteUpdatesImpl,
|
|
_batchedEventUpdatesImpl,
|
|
) {
|
|
batchedUpdatesImpl = _batchedUpdatesImpl;
|
|
discreteUpdatesImpl = _discreteUpdatesImpl;
|
|
flushDiscreteUpdatesImpl = _flushDiscreteUpdatesImpl;
|
|
batchedEventUpdatesImpl = _batchedEventUpdatesImpl;
|
|
}
|