Skip to content

Commit

Permalink
Add flow to SyntheticEvent (#19564)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
eps1lon and gaearon authored Aug 19, 2020
1 parent b8fa09e commit 87b3e2d
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 40 deletions.
14 changes: 7 additions & 7 deletions packages/react-dom/src/events/DOMPluginEventSystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ import {
SHOULD_NOT_PROCESS_POLYFILL_EVENT_PLUGINS,
} from './EventSystemFlags';
import type {AnyNativeEvent} from './PluginModuleType';
import type {ReactSyntheticEvent} from './ReactSyntheticEventType';
import type {
KnownReactSyntheticEvent,
ReactSyntheticEvent,
} from './ReactSyntheticEventType';
import type {ElementListenerMapEntry} from '../client/ReactDOMComponentTree';
import type {EventPriority} from 'shared/ReactTypes';
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
Expand Down Expand Up @@ -917,15 +920,12 @@ function getLowestCommonAncestor(instA: Fiber, instB: Fiber): Fiber | null {

function accumulateEnterLeaveListenersForEvent(
dispatchQueue: DispatchQueue,
event: ReactSyntheticEvent,
event: KnownReactSyntheticEvent,
target: Fiber,
common: Fiber | null,
inCapturePhase: boolean,
): void {
const registrationName = event._reactName;
if (registrationName === undefined) {
return;
}
const listeners: Array<DispatchListener> = [];

let instance = target;
Expand Down Expand Up @@ -969,8 +969,8 @@ function accumulateEnterLeaveListenersForEvent(
// phase event listeners.
export function accumulateEnterLeaveTwoPhaseListeners(
dispatchQueue: DispatchQueue,
leaveEvent: ReactSyntheticEvent,
enterEvent: null | ReactSyntheticEvent,
leaveEvent: KnownReactSyntheticEvent,
enterEvent: null | KnownReactSyntheticEvent,
from: Fiber | null,
to: Fiber | null,
): void {
Expand Down
18 changes: 15 additions & 3 deletions packages/react-dom/src/events/ReactSyntheticEventType.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,26 @@ export type DispatchConfig = {|
eventPriority?: EventPriority,
|};

export type ReactSyntheticEvent = {|
type BaseSyntheticEvent = {
isPersistent: () => boolean,
isPropagationStopped: () => boolean,
_dispatchInstances?: null | Array<Fiber | null> | Fiber,
_dispatchListeners?: null | Array<Function> | Function,
_reactName: string,
_targetInst: Fiber,
nativeEvent: Event,
target?: mixed,
relatedTarget?: mixed,
type: string,
currentTarget: null | EventTarget,
|};
};

export type KnownReactSyntheticEvent = BaseSyntheticEvent & {
_reactName: string,
};
export type UnknownReactSyntheticEvent = BaseSyntheticEvent & {
_reactName: null,
};

export type ReactSyntheticEvent =
| KnownReactSyntheticEvent
| UnknownReactSyntheticEvent;
38 changes: 23 additions & 15 deletions packages/react-dom/src/events/SyntheticEvent.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,23 @@
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

/* eslint valid-typeof: 0 */

import getEventCharCode from './getEventCharCode';

type EventInterfaceType = {
[propName: string]: 0 | ((event: {[propName: string]: mixed}) => mixed),
};

/**
* @interface Event
* @see http://www.w3.org/TR/DOM-Level-3-Events/
*/
const EventInterface = {
const EventInterface: EventInterfaceType = {
eventPhase: 0,
bubbles: 0,
cancelable: 0,
Expand Down Expand Up @@ -46,12 +52,12 @@ function functionThatReturnsFalse() {
* DOM interface; custom application-specific events can also subclass this.
*/
export function SyntheticEvent(
reactName,
reactEventType,
targetInst,
nativeEvent,
nativeEventTarget,
Interface = EventInterface,
reactName: string | null,
reactEventType: string,
targetInst: Fiber,
nativeEvent: {[propName: string]: mixed},
nativeEventTarget: null | EventTarget,
Interface: EventInterfaceType = EventInterface,
) {
this._reactName = reactName;
this._targetInst = targetInst;
Expand Down Expand Up @@ -95,6 +101,7 @@ Object.assign(SyntheticEvent.prototype, {

if (event.preventDefault) {
event.preventDefault();
// $FlowFixMe - flow is not aware of `unknown` in IE
} else if (typeof event.returnValue !== 'unknown') {
event.returnValue = false;
}
Expand All @@ -109,6 +116,7 @@ Object.assign(SyntheticEvent.prototype, {

if (event.stopPropagation) {
event.stopPropagation();
// $FlowFixMe - flow is not aware of `unknown` in IE
} else if (typeof event.cancelBubble !== 'unknown') {
// The ChangeEventPlugin registers a "propertychange" event for
// IE. This event does not support bubbling or cancelling, and
Expand Down Expand Up @@ -138,7 +146,7 @@ Object.assign(SyntheticEvent.prototype, {
isPersistent: functionThatReturnsTrue,
});

export const UIEventInterface = {
export const UIEventInterface: EventInterfaceType = {
...EventInterface,
view: 0,
detail: 0,
Expand All @@ -154,7 +162,7 @@ let isMovementYSet = false;
* @interface MouseEvent
* @see http://www.w3.org/TR/DOM-Level-3-Events/
*/
export const MouseEventInterface = {
export const MouseEventInterface: EventInterfaceType = {
...UIEventInterface,
screenX: 0,
screenY: 0,
Expand Down Expand Up @@ -213,7 +221,7 @@ export const MouseEventInterface = {
* @interface DragEvent
* @see http://www.w3.org/TR/DOM-Level-3-Events/
*/
export const DragEventInterface = {
export const DragEventInterface: EventInterfaceType = {
...MouseEventInterface,
dataTransfer: 0,
};
Expand All @@ -222,7 +230,7 @@ export const DragEventInterface = {
* @interface FocusEvent
* @see http://www.w3.org/TR/DOM-Level-3-Events/
*/
export const FocusEventInterface = {
export const FocusEventInterface: EventInterfaceType = {
...UIEventInterface,
relatedTarget: 0,
};
Expand All @@ -232,7 +240,7 @@ export const FocusEventInterface = {
* @see http://www.w3.org/TR/css3-animations/#AnimationEvent-interface
* @see https://developer.mozilla.org/en-US/docs/Web/API/AnimationEvent
*/
export const AnimationEventInterface = {
export const AnimationEventInterface: EventInterfaceType = {
...EventInterface,
animationName: 0,
elapsedTime: 0,
Expand All @@ -243,7 +251,7 @@ export const AnimationEventInterface = {
* @interface Event
* @see http://www.w3.org/TR/clipboard-apis/
*/
export const ClipboardEventInterface = {
export const ClipboardEventInterface: EventInterfaceType = {
...EventInterface,
clipboardData: function(event) {
return 'clipboardData' in event
Expand All @@ -256,7 +264,7 @@ export const ClipboardEventInterface = {
* @interface Event
* @see http://www.w3.org/TR/DOM-Level-3-Events/#events-compositionevents
*/
export const CompositionEventInterface = {
export const CompositionEventInterface: EventInterfaceType = {
...EventInterface,
data: 0,
};
Expand All @@ -267,7 +275,7 @@ export const CompositionEventInterface = {
* /#events-inputevents
*/
// Happens to share the same list for now.
export const InputEventInterface = CompositionEventInterface;
export const InputEventInterface: EventInterfaceType = CompositionEventInterface;

/**
* Normalization of deprecated HTML5 `key` values
Expand Down
31 changes: 16 additions & 15 deletions packages/react-dom/src/events/plugins/EnterLeaveEventPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
isContainerMarkedAsRoot,
} from '../../client/ReactDOMComponentTree';
import {accumulateEnterLeaveTwoPhaseListeners} from '../DOMPluginEventSystem';
import type {KnownReactSyntheticEvent} from '../ReactSyntheticEventType';

import {HostComponent, HostText} from 'react-reconciler/src/ReactWorkTags';
import {getNearestMountedFiber} from 'react-reconciler/src/ReactFiberTreeReflection';
Expand Down Expand Up @@ -147,23 +148,23 @@ function extractEvents(
leave.target = fromNode;
leave.relatedTarget = toNode;

let enter = new SyntheticEvent(
enterEventType,
eventTypePrefix + 'enter',
to,
nativeEvent,
nativeEventTarget,
eventInterface,
);
enter.target = toNode;
enter.relatedTarget = fromNode;
let enter: KnownReactSyntheticEvent | null = null;

// If we are not processing the first ancestor, then we
// should not process the same nativeEvent again, as we
// will have already processed it in the first ancestor.
// We should only process this nativeEvent if we are processing
// the first ancestor. Next time, we will ignore the event.
const nativeTargetInst = getClosestInstanceFromNode((nativeEventTarget: any));
if (nativeTargetInst !== targetInst) {
enter = null;
if (nativeTargetInst === targetInst) {
const enterEvent: KnownReactSyntheticEvent = new SyntheticEvent(
enterEventType,
eventTypePrefix + 'enter',
to,
nativeEvent,
nativeEventTarget,
eventInterface,
);
enterEvent.target = toNode;
enterEvent.relatedTarget = fromNode;
enter = enterEvent;
}

accumulateEnterLeaveTwoPhaseListeners(dispatchQueue, leave, enter, from, to);
Expand Down

0 comments on commit 87b3e2d

Please sign in to comment.