Skip to content

Commit

Permalink
Refactor createEventHandle signature (#19174)
Browse files Browse the repository at this point in the history
  • Loading branch information
trueadm authored Jul 7, 2020
1 parent 97b96da commit 4eb9b1d
Show file tree
Hide file tree
Showing 16 changed files with 498 additions and 390 deletions.
4 changes: 0 additions & 4 deletions packages/react-art/src/ReactARTHostConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -469,10 +469,6 @@ export function getInstanceFromNode(node) {
throw new Error('Not yet implemented.');
}

export function removeInstanceEventHandles(instance) {
// noop
}

export function isOpaqueHydratingObject(value: mixed): boolean {
throw new Error('Not yet implemented');
}
Expand Down
196 changes: 92 additions & 104 deletions packages/react-dom/src/client/ReactDOMEventHandle.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ type EventHandleOptions = {|
priority?: EventPriority,
|};

const PossiblyWeakSet = typeof WeakSet === 'function' ? WeakSet : Set;

function getNearestRootOrPortalContainer(node: Fiber): null | Element {
while (node !== null) {
const tag = node.tag;
Expand All @@ -72,12 +74,10 @@ function createEventHandleListener(
type: DOMTopLevelEventType,
capture: boolean,
callback: (SyntheticEvent<EventTarget>) => void,
destroy: (target: EventTarget | ReactScopeInstance) => void,
): ReactDOMEventHandleListener {
return {
callback,
capture,
destroy,
type,
};
}
Expand Down Expand Up @@ -111,6 +111,65 @@ function registerEventOnNearestTargetContainer(
);
}

function registerReactDOMEvent(
target: EventTarget | ReactScopeInstance,
topLevelType: DOMTopLevelEventType,
passive: boolean | void,
capture: boolean,
priority: EventPriority | void,
): void {
// Check if the target is a DOM element.
if ((target: any).nodeType === ELEMENT_NODE) {
const targetElement = ((target: any): Element);
// Check if the DOM element is managed by React.
const targetFiber = getClosestInstanceFromNode(targetElement);
if (targetFiber === null) {
invariant(
false,
'ReactDOM.createEventHandle: setListener called on an element ' +
'target that is not managed by React. Ensure React rendered the DOM element.',
);
}
registerEventOnNearestTargetContainer(
targetFiber,
topLevelType,
passive,
priority,
);
} else if (enableScopeAPI && isReactScope(target)) {
const scopeTarget = ((target: any): ReactScopeInstance);
const targetFiber = getFiberFromScopeInstance(scopeTarget);
if (targetFiber === null) {
// Scope is unmounted, do not proceed.
return;
}
registerEventOnNearestTargetContainer(
targetFiber,
topLevelType,
passive,
priority,
);
} else if (isValidEventTarget(target)) {
const eventTarget = ((target: any): EventTarget);
const listenerMap = getEventListenerMap(eventTarget);
listenToTopLevelEvent(
topLevelType,
eventTarget,
listenerMap,
PLUGIN_EVENT_SYSTEM | IS_TARGET_PHASE_ONLY,
capture,
passive,
priority,
);
} else {
invariant(
false,
'ReactDOM.createEventHandle: setter called on an invalid ' +
'target. Provide a valid EventTarget or an element managed by React.',
);
}
}

export function createEventHandle(
type: string,
options?: EventHandleOptions,
Expand Down Expand Up @@ -140,110 +199,39 @@ export function createEventHandle(
priority = getEventPriorityForListenerSystem(topLevelType);
}

const listeners = new Map();

const destroy = (target: EventTarget | ReactScopeInstance): void => {
const listener = listeners.get(target);
if (listener !== undefined) {
listeners.delete(target);
const targetListeners = getEventHandlerListeners(target);
if (targetListeners !== null) {
targetListeners.delete(listener);
}
const registeredReactDOMEvents = new PossiblyWeakSet();

return (
target: EventTarget | ReactScopeInstance,
callback: (SyntheticEvent<EventTarget>) => void,
) => {
invariant(
typeof callback === 'function',
'ReactDOM.createEventHandle: setter called with an invalid ' +
'callback. The callback must be a function.',
);
if (!registeredReactDOMEvents.has(target)) {
registeredReactDOMEvents.add(target);
registerReactDOMEvent(target, topLevelType, passive, capture, priority);
// Add the event to our known event types list.
addEventTypeToDispatchConfig(topLevelType);
}
};

const clear = (): void => {
const eventTargetsArr = Array.from(listeners.keys());
for (let i = 0; i < eventTargetsArr.length; i++) {
destroy(eventTargetsArr[i]);
const listener = createEventHandleListener(
topLevelType,
capture,
callback,
);
let targetListeners = getEventHandlerListeners(target);
if (targetListeners === null) {
targetListeners = new Set();
setEventHandlerListeners(target, targetListeners);
}
};

return {
setListener(
target: EventTarget | ReactScopeInstance,
callback: null | ((SyntheticEvent<EventTarget>) => void),
): void {
// Check if the target is a DOM element.
if ((target: any).nodeType === ELEMENT_NODE) {
const targetElement = ((target: any): Element);
// Check if the DOM element is managed by React.
const targetFiber = getClosestInstanceFromNode(targetElement);
if (targetFiber === null) {
invariant(
false,
'ReactDOM.createEventHandle: setListener called on an element ' +
'target that is not managed by React. Ensure React rendered the DOM element.',
);
}
registerEventOnNearestTargetContainer(
targetFiber,
topLevelType,
passive,
priority,
);
} else if (enableScopeAPI && isReactScope(target)) {
const scopeTarget = ((target: any): ReactScopeInstance);
const targetFiber = getFiberFromScopeInstance(scopeTarget);
if (targetFiber === null) {
// Scope is unmounted, do not proceed.
return;
}
registerEventOnNearestTargetContainer(
targetFiber,
topLevelType,
passive,
priority,
);
} else if (isValidEventTarget(target)) {
const eventTarget = ((target: any): EventTarget);
const listenerMap = getEventListenerMap(eventTarget);
listenToTopLevelEvent(
topLevelType,
eventTarget,
listenerMap,
PLUGIN_EVENT_SYSTEM | IS_TARGET_PHASE_ONLY,
capture,
passive,
priority,
);
} else {
invariant(
false,
'ReactDOM.createEventHandle: setListener called on an invalid ' +
'target. Provide a valid EventTarget or an element managed by React.',
);
}
let listener = listeners.get(target);
if (listener === undefined) {
if (callback === null) {
return;
}
listener = createEventHandleListener(
topLevelType,
capture,
callback,
destroy,
);
listeners.set(target, listener);

let targetListeners = getEventHandlerListeners(target);
if (targetListeners === null) {
targetListeners = new Set();
setEventHandlerListeners(target, targetListeners);
}
targetListeners.add(listener);
// Finally, add the event to our known event types list.
addEventTypeToDispatchConfig(topLevelType);
} else if (callback !== null) {
listener.callback = callback;
} else {
// Remove listener
destroy(target);
}
},
clear,
targetListeners.add(listener);
return () => {
((targetListeners: any): Set<ReactDOMEventHandleListener>).delete(
listener,
);
};
};
}
return (null: any);
Expand Down
21 changes: 1 addition & 20 deletions packages/react-dom/src/client/ReactDOMHostConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,7 @@ import {
} from 'shared/ReactFeatureFlags';
import {HostComponent, HostText} from 'react-reconciler/src/ReactWorkTags';
import {TOP_BEFORE_BLUR, TOP_AFTER_BLUR} from '../events/DOMTopLevelEventTypes';
import {
listenToReactPropEvent,
clearEventHandleListenersForTarget,
} from '../events/DOMModernPluginEventSystem';
import {listenToReactPropEvent} from '../events/DOMModernPluginEventSystem';

export type Type = string;
export type Props = {
Expand Down Expand Up @@ -534,14 +531,6 @@ function dispatchAfterDetachedBlur(target: HTMLElement): void {
}
}

export function removeInstanceEventHandles(
instance: Instance | TextInstance | SuspenseInstance,
) {
if (enableCreateEventHandleAPI) {
clearEventHandleListenersForTarget(instance);
}
}

export function removeChild(
parentInstance: Instance,
child: Instance | TextInstance | SuspenseInstance,
Expand Down Expand Up @@ -1134,14 +1123,6 @@ export function prepareScopeUpdate(
}
}

export function removeScopeEventHandles(
scopeInstance: ReactScopeInstance,
): void {
if (enableScopeAPI && enableCreateEventHandleAPI) {
clearEventHandleListenersForTarget(scopeInstance);
}
}

export function getInstanceFromScope(
scopeInstance: ReactScopeInstance,
): null | Object {
Expand Down
20 changes: 1 addition & 19 deletions packages/react-dom/src/events/DOMModernPluginEventSystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import type {
ElementListenerMap,
ElementListenerMapEntry,
} from '../client/ReactDOMComponentTree';
import type {EventPriority, ReactScopeInstance} from 'shared/ReactTypes';
import type {EventPriority} from 'shared/ReactTypes';
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';

import {registrationNameDependencies} from './EventRegistry';
Expand Down Expand Up @@ -954,24 +954,6 @@ export function addEventTypeToDispatchConfig(type: DOMTopLevelEventType): void {
}
}

export function clearEventHandleListenersForTarget(
target: EventTarget | ReactScopeInstance,
): void {
// It's unfortunate that we have to do this cleanup, but
// it's necessary otherwise we will leak the host instances
// on the createEventHandle API "listeners" Map. We call destroy
// on each listener to ensure we properly remove the instance
// from the listeners Map. Note: we have this Map so that we
// can track listeners for the handle.clear() API call.
const listeners = getEventHandlerListeners(target);
if (listeners !== null) {
const listenersArr = Array.from(listeners);
for (let i = 0; i < listenersArr.length; i++) {
listenersArr[i].destroy(target);
}
}
}

export function getListenerMapKey(
topLevelType: DOMTopLevelEventType,
capture: boolean,
Expand Down
Loading

0 comments on commit 4eb9b1d

Please sign in to comment.