Add flow to SyntheticEvent (#19564)
* Add flow to SyntheticEvent * Minimal implementation of known and unknown synthetic events * less casting * Update EnterLeaveEventPlugin.js Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
Sebastian Silbermann committed
Aug 19, 2020 at 15:16 UTC
87b3e2d257e49b6d2c8e662830fc8f3c7d62f85f
4 files changed
+61
-40
packages/react-dom/src/events/DOMPluginEventSystem.js
+7
-7
@@ -15,7 +15,10 @@ import {
15
SHOULD_NOT_PROCESS_POLYFILL_EVENT_PLUGINS,
16
} from './EventSystemFlags';
17
import type {AnyNativeEvent} from './PluginModuleType';
18
-import type {ReactSyntheticEvent} from './ReactSyntheticEventType';
18
+import type {
19
+ KnownReactSyntheticEvent,
20
+ ReactSyntheticEvent,
21
+} from './ReactSyntheticEventType';
22
import type {ElementListenerMapEntry} from '../client/ReactDOMComponentTree';
23
import type {EventPriority} from 'shared/ReactTypes';
24
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
@@ -917,15 +920,12 @@ function getLowestCommonAncestor(instA: Fiber, instB: Fiber): Fiber | null {
920
921
function accumulateEnterLeaveListenersForEvent(
922
dispatchQueue: DispatchQueue,
920
- event: ReactSyntheticEvent,
923
+ event: KnownReactSyntheticEvent,
924
target: Fiber,
925
common: Fiber | null,
926
inCapturePhase: boolean,
927
): void {
928
const registrationName = event._reactName;
926
- if (registrationName === undefined) {
927
- return;
928
- }
929
const listeners: Array<DispatchListener> = [];
930
931
let instance = target;
@@ -969,8 +969,8 @@ function accumulateEnterLeaveListenersForEvent(
969
// phase event listeners.
970
export function accumulateEnterLeaveTwoPhaseListeners(
971
dispatchQueue: DispatchQueue,
972
- leaveEvent: ReactSyntheticEvent,
973
- enterEvent: null | ReactSyntheticEvent,
972
+ leaveEvent: KnownReactSyntheticEvent,
973
+ enterEvent: null | KnownReactSyntheticEvent,
974
from: Fiber | null,
975
to: Fiber | null,
976
): void {
packages/react-dom/src/events/ReactSyntheticEventType.js
+15
-3
@@ -22,14 +22,26 @@ export type DispatchConfig = {|
22
eventPriority?: EventPriority,
23
|};
24
25
-export type ReactSyntheticEvent = {|
25
+type BaseSyntheticEvent = {
26
isPersistent: () => boolean,
27
isPropagationStopped: () => boolean,
28
_dispatchInstances?: null | Array<Fiber | null> | Fiber,
29
_dispatchListeners?: null | Array<Function> | Function,
30
- _reactName: string,
30
_targetInst: Fiber,
31
nativeEvent: Event,
32
+ target?: mixed,
33
+ relatedTarget?: mixed,
34
type: string,
35
currentTarget: null | EventTarget,
35
-|};
36
+};
37
+
38
+export type KnownReactSyntheticEvent = BaseSyntheticEvent & {
39
+ _reactName: string,
40
+};
41
+export type UnknownReactSyntheticEvent = BaseSyntheticEvent & {
42
+ _reactName: null,
43
+};
44
+
45
+export type ReactSyntheticEvent =
46
+ | KnownReactSyntheticEvent
47
+ | UnknownReactSyntheticEvent;
packages/react-dom/src/events/SyntheticEvent.js
+23
-15
@@ -3,17 +3,23 @@
3
*
4
* This source code is licensed under the MIT license found in the
5
* LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
*/
9
10
/* eslint valid-typeof: 0 */
11
12
import getEventCharCode from './getEventCharCode';
13
14
+type EventInterfaceType = {
15
+ [propName: string]: 0 | ((event: {[propName: string]: mixed}) => mixed),
16
+};
17
+
18
/**
19
* @interface Event
20
* @see http://www.w3.org/TR/DOM-Level-3-Events/
21
*/
16
-const EventInterface = {
22
+const EventInterface: EventInterfaceType = {
23
eventPhase: 0,
24
bubbles: 0,
25
cancelable: 0,
@@ -46,12 +52,12 @@ function functionThatReturnsFalse() {
52
* DOM interface; custom application-specific events can also subclass this.
53
*/
54
export function SyntheticEvent(
49
- reactName,
50
- reactEventType,
51
- targetInst,
52
- nativeEvent,
53
- nativeEventTarget,
54
- Interface = EventInterface,
55
+ reactName: string | null,
56
+ reactEventType: string,
57
+ targetInst: Fiber,
58
+ nativeEvent: {[propName: string]: mixed},
59
+ nativeEventTarget: null | EventTarget,
60
+ Interface: EventInterfaceType = EventInterface,
61
) {
62
this._reactName = reactName;
63
this._targetInst = targetInst;
@@ -95,6 +101,7 @@ Object.assign(SyntheticEvent.prototype, {
101
102
if (event.preventDefault) {
103
event.preventDefault();
104
+ // $FlowFixMe - flow is not aware of `unknown` in IE
105
} else if (typeof event.returnValue !== 'unknown') {
106
event.returnValue = false;
107
}
@@ -109,6 +116,7 @@ Object.assign(SyntheticEvent.prototype, {
116
117
if (event.stopPropagation) {
118
event.stopPropagation();
119
+ // $FlowFixMe - flow is not aware of `unknown` in IE
120
} else if (typeof event.cancelBubble !== 'unknown') {
121
// The ChangeEventPlugin registers a "propertychange" event for
122
// IE. This event does not support bubbling or cancelling, and
@@ -138,7 +146,7 @@ Object.assign(SyntheticEvent.prototype, {
146
isPersistent: functionThatReturnsTrue,
147
});
148
141
-export const UIEventInterface = {
149
+export const UIEventInterface: EventInterfaceType = {
150
...EventInterface,
151
view: 0,
152
detail: 0,
@@ -154,7 +162,7 @@ let isMovementYSet = false;
162
* @interface MouseEvent
163
* @see http://www.w3.org/TR/DOM-Level-3-Events/
164
*/
157
-export const MouseEventInterface = {
165
+export const MouseEventInterface: EventInterfaceType = {
166
...UIEventInterface,
167
screenX: 0,
168
screenY: 0,
@@ -213,7 +221,7 @@ export const MouseEventInterface = {
221
* @interface DragEvent
222
* @see http://www.w3.org/TR/DOM-Level-3-Events/
223
*/
216
-export const DragEventInterface = {
224
+export const DragEventInterface: EventInterfaceType = {
225
...MouseEventInterface,
226
dataTransfer: 0,
227
};
@@ -222,7 +230,7 @@ export const DragEventInterface = {
230
* @interface FocusEvent
231
* @see http://www.w3.org/TR/DOM-Level-3-Events/
232
*/
225
-export const FocusEventInterface = {
233
+export const FocusEventInterface: EventInterfaceType = {
234
...UIEventInterface,
235
relatedTarget: 0,
236
};
@@ -232,7 +240,7 @@ export const FocusEventInterface = {
240
* @see http://www.w3.org/TR/css3-animations/#AnimationEvent-interface
241
* @see https://developer.mozilla.org/en-US/docs/Web/API/AnimationEvent
242
*/
235
-export const AnimationEventInterface = {
243
+export const AnimationEventInterface: EventInterfaceType = {
244
...EventInterface,
245
animationName: 0,
246
elapsedTime: 0,
@@ -243,7 +251,7 @@ export const AnimationEventInterface = {
251
* @interface Event
252
* @see http://www.w3.org/TR/clipboard-apis/
253
*/
246
-export const ClipboardEventInterface = {
254
+export const ClipboardEventInterface: EventInterfaceType = {
255
...EventInterface,
256
clipboardData: function(event) {
257
return 'clipboardData' in event
@@ -256,7 +264,7 @@ export const ClipboardEventInterface = {
264
* @interface Event
265
* @see http://www.w3.org/TR/DOM-Level-3-Events/#events-compositionevents
266
*/
259
-export const CompositionEventInterface = {
267
+export const CompositionEventInterface: EventInterfaceType = {
268
...EventInterface,
269
data: 0,
270
};
@@ -267,7 +275,7 @@ export const CompositionEventInterface = {
275
* /#events-inputevents
276
*/
277
// Happens to share the same list for now.
270
-export const InputEventInterface = CompositionEventInterface;
278
+export const InputEventInterface: EventInterfaceType = CompositionEventInterface;
279
280
/**
281
* Normalization of deprecated HTML5 `key` values
packages/react-dom/src/events/plugins/EnterLeaveEventPlugin.js
+16
-15
@@ -25,6 +25,7 @@ import {
25
isContainerMarkedAsRoot,
26
} from '../../client/ReactDOMComponentTree';
27
import {accumulateEnterLeaveTwoPhaseListeners} from '../DOMPluginEventSystem';
28
+import type {KnownReactSyntheticEvent} from '../ReactSyntheticEventType';
29
30
import {HostComponent, HostText} from 'react-reconciler/src/ReactWorkTags';
31
import {getNearestMountedFiber} from 'react-reconciler/src/ReactFiberTreeReflection';
@@ -147,23 +148,23 @@ function extractEvents(
148
leave.target = fromNode;
149
leave.relatedTarget = toNode;
150
150
- let enter = new SyntheticEvent(
151
- enterEventType,
152
- eventTypePrefix + 'enter',
153
- to,
154
- nativeEvent,
155
- nativeEventTarget,
156
- eventInterface,
157
- );
158
- enter.target = toNode;
159
- enter.relatedTarget = fromNode;
151
+ let enter: KnownReactSyntheticEvent | null = null;
152
161
- // If we are not processing the first ancestor, then we
162
- // should not process the same nativeEvent again, as we
163
- // will have already processed it in the first ancestor.
153
+ // We should only process this nativeEvent if we are processing
154
+ // the first ancestor. Next time, we will ignore the event.
155
const nativeTargetInst = getClosestInstanceFromNode((nativeEventTarget: any));
165
- if (nativeTargetInst !== targetInst) {
166
- enter = null;
156
+ if (nativeTargetInst === targetInst) {
157
+ const enterEvent: KnownReactSyntheticEvent = new SyntheticEvent(
158
+ enterEventType,
159
+ eventTypePrefix + 'enter',
160
+ to,
161
+ nativeEvent,
162
+ nativeEventTarget,
163
+ eventInterface,
164
+ );
165
+ enterEvent.target = toNode;
166
+ enterEvent.relatedTarget = fromNode;
167
+ enter = enterEvent;
168
}
169
170
accumulateEnterLeaveTwoPhaseListeners(dispatchQueue, leave, enter, from, to);