Skip to content

Commit a387566

Browse files
authored
React Event System: cleanup plugins + break out update batching logic (facebook#18503)
1 parent 717a33a commit a387566

13 files changed

+346
-129
lines changed

packages/legacy-events/EventPluginRegistry.js

+17
Original file line numberDiff line numberDiff line change
@@ -241,3 +241,20 @@ export function injectEventPluginsByName(
241241
recomputePluginOrdering();
242242
}
243243
}
244+
245+
export function injectEventPlugins(
246+
eventPlugins: [PluginModule<AnyNativeEvent>],
247+
): void {
248+
for (let i = 0; i < eventPlugins.length; i++) {
249+
const pluginModule = eventPlugins[i];
250+
plugins.push(pluginModule);
251+
const publishedEvents = pluginModule.eventTypes;
252+
for (const eventName in publishedEvents) {
253+
publishEventForPlugin(
254+
publishedEvents[eventName],
255+
pluginModule,
256+
eventName,
257+
);
258+
}
259+
}
260+
}

packages/legacy-events/ReactGenericBatching.js

+1-58
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,6 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8-
import {
9-
needsStateRestore,
10-
restoreStateIfNeeded,
11-
} from './ReactControlledComponent';
12-
13-
import {enableDeprecatedFlareAPI} from 'shared/ReactFeatureFlags';
14-
import {invokeGuardedCallbackAndCatchFirstError} from 'shared/ReactErrorUtils';
15-
168
// Used as a way to call batchedUpdates when we don't have a reference to
179
// the renderer. Such as when we're dispatching events or if third party
1810
// libraries need to call batchedUpdates. Eventually, this API will go away when
@@ -32,21 +24,6 @@ let batchedEventUpdatesImpl = batchedUpdatesImpl;
3224
let isInsideEventHandler = false;
3325
let isBatchingEventUpdates = false;
3426

35-
function finishEventHandler() {
36-
// Here we wait until all updates have propagated, which is important
37-
// when using controlled components within layers:
38-
// https://github.com/facebook/react/issues/1698
39-
// Then we restore state of any controlled component.
40-
const controlledComponentsHavePendingUpdates = needsStateRestore();
41-
if (controlledComponentsHavePendingUpdates) {
42-
// If a controlled event was fired, we may need to restore the state of
43-
// the DOM node back to the controlled value. This is necessary when React
44-
// bails out of the update without touching the DOM.
45-
flushDiscreteUpdatesImpl();
46-
restoreStateIfNeeded();
47-
}
48-
}
49-
5027
export function batchedUpdates(fn, bookkeeping) {
5128
if (isInsideEventHandler) {
5229
// If we are currently inside another batch, we need to wait until it
@@ -58,7 +35,6 @@ export function batchedUpdates(fn, bookkeeping) {
5835
return batchedUpdatesImpl(fn, bookkeeping);
5936
} finally {
6037
isInsideEventHandler = false;
61-
finishEventHandler();
6238
}
6339
}
6440

@@ -73,19 +49,6 @@ export function batchedEventUpdates(fn, a, b) {
7349
return batchedEventUpdatesImpl(fn, a, b);
7450
} finally {
7551
isBatchingEventUpdates = false;
76-
finishEventHandler();
77-
}
78-
}
79-
80-
// This is for the React Flare event system
81-
export function executeUserEventHandler(fn: any => void, value: any): void {
82-
const previouslyInEventHandler = isInsideEventHandler;
83-
try {
84-
isInsideEventHandler = true;
85-
const type = typeof value === 'object' && value !== null ? value.type : '';
86-
invokeGuardedCallbackAndCatchFirstError(type, fn, undefined, value);
87-
} finally {
88-
isInsideEventHandler = previouslyInEventHandler;
8952
}
9053
}
9154

@@ -97,32 +60,12 @@ export function discreteUpdates(fn, a, b, c, d) {
9760
} finally {
9861
isInsideEventHandler = prevIsInsideEventHandler;
9962
if (!isInsideEventHandler) {
100-
finishEventHandler();
10163
}
10264
}
10365
}
10466

105-
let lastFlushedEventTimeStamp = 0;
10667
export function flushDiscreteUpdatesIfNeeded(timeStamp: number) {
107-
// event.timeStamp isn't overly reliable due to inconsistencies in
108-
// how different browsers have historically provided the time stamp.
109-
// Some browsers provide high-resolution time stamps for all events,
110-
// some provide low-resolution time stamps for all events. FF < 52
111-
// even mixes both time stamps together. Some browsers even report
112-
// negative time stamps or time stamps that are 0 (iOS9) in some cases.
113-
// Given we are only comparing two time stamps with equality (!==),
114-
// we are safe from the resolution differences. If the time stamp is 0
115-
// we bail-out of preventing the flush, which can affect semantics,
116-
// such as if an earlier flush removes or adds event listeners that
117-
// are fired in the subsequent flush. However, this is the same
118-
// behaviour as we had before this change, so the risks are low.
119-
if (
120-
!isInsideEventHandler &&
121-
(!enableDeprecatedFlareAPI ||
122-
timeStamp === 0 ||
123-
lastFlushedEventTimeStamp !== timeStamp)
124-
) {
125-
lastFlushedEventTimeStamp = timeStamp;
68+
if (!isInsideEventHandler) {
12669
flushDiscreteUpdatesImpl();
12770
}
12871
}

packages/react-dom/src/client/ReactDOM.js

+6-8
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,6 @@ import {
3939
} from 'react-reconciler/src/ReactFiberReconciler';
4040
import {createPortal as createPortalImpl} from 'react-reconciler/src/ReactPortal';
4141
import {canUseDOM} from 'shared/ExecutionEnvironment';
42-
import {setBatchingImplementation} from 'legacy-events/ReactGenericBatching';
43-
import {
44-
setRestoreImplementation,
45-
enqueueStateRestore,
46-
restoreStateIfNeeded,
47-
} from 'legacy-events/ReactControlledComponent';
48-
import {runEventsInBatch} from 'legacy-events/EventBatching';
4942
import {
5043
eventNameDispatchConfigs,
5144
injectEventPluginsByName,
@@ -69,6 +62,12 @@ import {
6962
setAttemptHydrationAtCurrentPriority,
7063
queueExplicitHydrationTarget,
7164
} from '../events/ReactDOMEventReplaying';
65+
import {setBatchingImplementation} from '../events/ReactDOMUpdateBatching';
66+
import {
67+
setRestoreImplementation,
68+
enqueueStateRestore,
69+
restoreStateIfNeeded,
70+
} from '../events/ReactDOMControlledComponent';
7271

7372
setAttemptSynchronousHydration(attemptSynchronousHydration);
7473
setAttemptUserBlockingHydration(attemptUserBlockingHydration);
@@ -183,7 +182,6 @@ const Internals = {
183182
enqueueStateRestore,
184183
restoreStateIfNeeded,
185184
dispatchEvent,
186-
runEventsInBatch,
187185
flushPassiveEffects,
188186
IsThisRendererActing,
189187
],

packages/react-dom/src/client/ReactDOMClientInjection.js

+48-36
Original file line numberDiff line numberDiff line change
@@ -20,43 +20,55 @@ import SimpleEventPlugin from '../events/SimpleEventPlugin';
2020
import {
2121
injectEventPluginOrder,
2222
injectEventPluginsByName,
23+
injectEventPlugins,
2324
} from 'legacy-events/EventPluginRegistry';
25+
import {enableModernEventSystem} from 'shared/ReactFeatureFlags';
2426

25-
/**
26-
* Specifies a deterministic ordering of `EventPlugin`s. A convenient way to
27-
* reason about plugins, without having to package every one of them. This
28-
* is better than having plugins be ordered in the same order that they
29-
* are injected because that ordering would be influenced by the packaging order.
30-
* `ResponderEventPlugin` must occur before `SimpleEventPlugin` so that
31-
* preventing default on events is convenient in `SimpleEventPlugin` handlers.
32-
*/
33-
const DOMEventPluginOrder = [
34-
'ResponderEventPlugin',
35-
'SimpleEventPlugin',
36-
'EnterLeaveEventPlugin',
37-
'ChangeEventPlugin',
38-
'SelectEventPlugin',
39-
'BeforeInputEventPlugin',
40-
];
27+
if (enableModernEventSystem) {
28+
injectEventPlugins([
29+
SimpleEventPlugin,
30+
EnterLeaveEventPlugin,
31+
ChangeEventPlugin,
32+
SelectEventPlugin,
33+
BeforeInputEventPlugin,
34+
]);
35+
} else {
36+
/**
37+
* Specifies a deterministic ordering of `EventPlugin`s. A convenient way to
38+
* reason about plugins, without having to package every one of them. This
39+
* is better than having plugins be ordered in the same order that they
40+
* are injected because that ordering would be influenced by the packaging order.
41+
* `ResponderEventPlugin` must occur before `SimpleEventPlugin` so that
42+
* preventing default on events is convenient in `SimpleEventPlugin` handlers.
43+
*/
44+
const DOMEventPluginOrder = [
45+
'ResponderEventPlugin',
46+
'SimpleEventPlugin',
47+
'EnterLeaveEventPlugin',
48+
'ChangeEventPlugin',
49+
'SelectEventPlugin',
50+
'BeforeInputEventPlugin',
51+
];
4152

42-
/**
43-
* Inject modules for resolving DOM hierarchy and plugin ordering.
44-
*/
45-
injectEventPluginOrder(DOMEventPluginOrder);
46-
setComponentTree(
47-
getFiberCurrentPropsFromNode,
48-
getInstanceFromNode,
49-
getNodeFromInstance,
50-
);
53+
/**
54+
* Inject modules for resolving DOM hierarchy and plugin ordering.
55+
*/
56+
injectEventPluginOrder(DOMEventPluginOrder);
57+
setComponentTree(
58+
getFiberCurrentPropsFromNode,
59+
getInstanceFromNode,
60+
getNodeFromInstance,
61+
);
5162

52-
/**
53-
* Some important event plugins included by default (without having to require
54-
* them).
55-
*/
56-
injectEventPluginsByName({
57-
SimpleEventPlugin: SimpleEventPlugin,
58-
EnterLeaveEventPlugin: EnterLeaveEventPlugin,
59-
ChangeEventPlugin: ChangeEventPlugin,
60-
SelectEventPlugin: SelectEventPlugin,
61-
BeforeInputEventPlugin: BeforeInputEventPlugin,
62-
});
63+
/**
64+
* Some important event plugins included by default (without having to require
65+
* them).
66+
*/
67+
injectEventPluginsByName({
68+
SimpleEventPlugin: SimpleEventPlugin,
69+
EnterLeaveEventPlugin: EnterLeaveEventPlugin,
70+
ChangeEventPlugin: ChangeEventPlugin,
71+
SelectEventPlugin: SelectEventPlugin,
72+
BeforeInputEventPlugin: BeforeInputEventPlugin,
73+
});
74+
}

packages/react-dom/src/events/ChangeEventPlugin.js

+12-4
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
*/
77

88
import {runEventsInBatch} from 'legacy-events/EventBatching';
9-
import {enqueueStateRestore} from 'legacy-events/ReactControlledComponent';
10-
import {batchedUpdates} from 'legacy-events/ReactGenericBatching';
119
import SyntheticEvent from 'legacy-events/SyntheticEvent';
1210
import isTextInputElement from './isTextInputElement';
1311
import {canUseDOM} from 'shared/ExecutionEnvironment';
@@ -27,9 +25,15 @@ import isEventSupported from './isEventSupported';
2725
import {getNodeFromInstance} from '../client/ReactDOMComponentTree';
2826
import {updateValueIfChanged} from '../client/inputValueTracking';
2927
import {setDefaultValue} from '../client/ReactDOMInput';
28+
import {enqueueStateRestore} from './ReactDOMControlledComponent';
3029

31-
import {disableInputAttributeSyncing} from 'shared/ReactFeatureFlags';
30+
import {
31+
disableInputAttributeSyncing,
32+
enableModernEventSystem,
33+
} from 'shared/ReactFeatureFlags';
3234
import accumulateTwoPhaseListeners from './accumulateTwoPhaseListeners';
35+
import {batchedUpdates} from './ReactDOMUpdateBatching';
36+
import {dispatchEventsInBatch} from './DOMModernPluginEventSystem';
3337

3438
const eventTypes = {
3539
change: {
@@ -101,7 +105,11 @@ function manualDispatchChangeEvent(nativeEvent) {
101105
}
102106

103107
function runEventInBatch(event) {
104-
runEventsInBatch(event);
108+
if (enableModernEventSystem) {
109+
dispatchEventsInBatch([event]);
110+
} else {
111+
runEventsInBatch(event);
112+
}
105113
}
106114

107115
function getInstIfValueChanged(targetInst) {

packages/react-dom/src/events/DOMLegacyEventPluginSystem.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import {
2222
HostText,
2323
} from 'react-reconciler/src/ReactWorkTags';
2424
import {IS_FIRST_ANCESTOR, PLUGIN_EVENT_SYSTEM} from './EventSystemFlags';
25-
import {batchedEventUpdates} from 'legacy-events/ReactGenericBatching';
2625
import {runEventsInBatch} from 'legacy-events/EventBatching';
2726
import {plugins} from 'legacy-events/EventPluginRegistry';
2827
import accumulateInto from 'legacy-events/accumulateInto';
@@ -45,6 +44,7 @@ import {
4544
mediaEventTypes,
4645
} from './DOMTopLevelEventTypes';
4746
import {addTrappedEventListener} from './ReactDOMEventListener';
47+
import {batchedEventUpdates} from './ReactDOMUpdateBatching';
4848

4949
/**
5050
* Summary of `DOMEventPluginSystem` event handling:

packages/react-dom/src/events/DOMModernPluginEventSystem.js

+21-10
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import type {
2424
import type {ReactDOMListener} from '../shared/ReactDOMTypes';
2525

2626
import {registrationNameDependencies} from 'legacy-events/EventPluginRegistry';
27-
import {batchedEventUpdates} from 'legacy-events/ReactGenericBatching';
2827
import {plugins} from 'legacy-events/EventPluginRegistry';
2928
import {
3029
PLUGIN_EVENT_SYSTEM,
@@ -86,12 +85,16 @@ import {
8685
} from '../client/ReactDOMComponentTree';
8786
import {COMMENT_NODE} from '../shared/HTMLNodeType';
8887
import {topLevelEventsToDispatchConfig} from './DOMEventProperties';
88+
import {batchedEventUpdates} from './ReactDOMUpdateBatching';
8989

9090
import {
9191
enableLegacyFBSupport,
9292
enableUseEventAPI,
9393
} from 'shared/ReactFeatureFlags';
94-
import {invokeGuardedCallbackAndCatchFirstError} from 'shared/ReactErrorUtils';
94+
import {
95+
invokeGuardedCallbackAndCatchFirstError,
96+
rethrowCaughtError,
97+
} from 'shared/ReactErrorUtils';
9598

9699
const capturePhaseEvents = new Set([
97100
TOP_FOCUS,
@@ -213,6 +216,21 @@ function executeDispatchesInOrder(event: ReactSyntheticEvent): void {
213216
event._dispatchCurrentTargets = null;
214217
}
215218

219+
export function dispatchEventsInBatch(
220+
events: Array<ReactSyntheticEvent>,
221+
): void {
222+
for (let i = 0; i < events.length; i++) {
223+
const syntheticEvent = events[i];
224+
executeDispatchesInOrder(syntheticEvent);
225+
// Release the event from the pool if needed
226+
if (!syntheticEvent.isPersistent()) {
227+
syntheticEvent.constructor.release(syntheticEvent);
228+
}
229+
}
230+
// This would be a good time to rethrow if any of the event handlers threw.
231+
rethrowCaughtError();
232+
}
233+
216234
function dispatchEventsForPlugins(
217235
topLevelType: DOMTopLevelEventType,
218236
eventSystemFlags: EventSystemFlags,
@@ -244,14 +262,7 @@ function dispatchEventsForPlugins(
244262
}
245263
}
246264
}
247-
for (let i = 0; i < syntheticEvents.length; i++) {
248-
const syntheticEvent = syntheticEvents[i];
249-
executeDispatchesInOrder(syntheticEvent);
250-
// Release the event from the pool if needed
251-
if (!syntheticEvent.isPersistent()) {
252-
syntheticEvent.constructor.release(syntheticEvent);
253-
}
254-
}
265+
dispatchEventsInBatch(syntheticEvents);
255266
}
256267

257268
function shouldUpgradeListener(

packages/react-dom/src/events/DeprecatedDOMEventResponderSystem.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,13 @@ import {
3030
discreteUpdates,
3131
flushDiscreteUpdatesIfNeeded,
3232
executeUserEventHandler,
33-
} from 'legacy-events/ReactGenericBatching';
34-
import {enqueueStateRestore} from 'legacy-events/ReactControlledComponent';
33+
} from './ReactDOMUpdateBatching';
3534
import type {Fiber} from 'react-reconciler/src/ReactFiber';
3635
import {enableDeprecatedFlareAPI} from 'shared/ReactFeatureFlags';
3736
import invariant from 'shared/invariant';
3837

3938
import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree';
39+
import {enqueueStateRestore} from './ReactDOMControlledComponent';
4040
import {
4141
ContinuousEvent,
4242
UserBlockingEvent,

0 commit comments

Comments
 (0)