Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Removed unused imperative events implementation from React Native renderer #26282

Merged
merged 1 commit into from
Mar 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/react-native-renderer/src/ReactFabricEventEmitter.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ import {
import {batchedUpdates} from './legacy-events/ReactGenericBatching';
import accumulateInto from './legacy-events/accumulateInto';

import getListeners from './ReactNativeGetListeners';
import getListener from './ReactNativeGetListener';
import {runEventsInBatch} from './legacy-events/EventBatching';

import {RawEventEmitter} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';

export {getListeners, registrationNameModules as registrationNames};
export {getListener, registrationNameModules as registrationNames};

/**
* Allows registered plugins an opportunity to extract events from top-level
Expand Down
122 changes: 0 additions & 122 deletions packages/react-native-renderer/src/ReactFabricHostConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,28 +99,6 @@ export type RendererInspectionConfig = $ReadOnly<{
) => void,
}>;

// TODO?: find a better place for this type to live
export type EventListenerOptions = $ReadOnly<{
capture?: boolean,
once?: boolean,
passive?: boolean,
signal: mixed, // not yet implemented
}>;
export type EventListenerRemoveOptions = $ReadOnly<{
capture?: boolean,
}>;

// TODO?: this will be changed in the future to be w3c-compatible and allow "EventListener" objects as well as functions.
export type EventListener = Function;

type InternalEventListeners = {
[string]: {
listener: EventListener,
options: EventListenerOptions,
invalidated: boolean,
}[],
};

// TODO: Remove this conditional once all changes have propagated.
if (registerEventHandler) {
/**
Expand All @@ -137,7 +115,6 @@ class ReactFabricHostComponent {
viewConfig: ViewConfig;
currentProps: Props;
_internalInstanceHandle: Object;
_eventListeners: ?InternalEventListeners;

constructor(
tag: number,
Expand Down Expand Up @@ -236,105 +213,6 @@ class ReactFabricHostComponent {
setNativeProps(stateNode.node, updatePayload);
}
}

// This API (addEventListener, removeEventListener) attempts to adhere to the
// w3 Level2 Events spec as much as possible, treating HostComponent as a DOM node.
//
// Unless otherwise noted, these methods should "just work" and adhere to the W3 specs.
// If they deviate in a way that is not explicitly noted here, you've found a bug!
//
// See:
// * https://www.w3.org/TR/DOM-Level-2-Events/events.html
// * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
// * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener
//
// And notably, not implemented (yet?):
// * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/dispatchEvent
//
//
// Deviations from spec/TODOs:
// (1) listener must currently be a function, we do not support EventListener objects yet.
// (2) we do not support the `signal` option / AbortSignal yet
addEventListener_unstable(
eventType: string,
listener: EventListener,
options: EventListenerOptions | boolean,
// $FlowFixMe[missing-local-annot]
) {
if (typeof eventType !== 'string') {
throw new Error('addEventListener_unstable eventType must be a string');
}
if (typeof listener !== 'function') {
throw new Error('addEventListener_unstable listener must be a function');
}

// The third argument is either boolean indicating "captures" or an object.
const optionsObj =
typeof options === 'object' && options !== null ? options : {};
const capture =
(typeof options === 'boolean' ? options : optionsObj.capture) || false;
const once = optionsObj.once || false;
const passive = optionsObj.passive || false;
const signal = null; // TODO: implement signal/AbortSignal

/* $FlowFixMe the old version of Flow doesn't have a good way to define an
* empty exact object. */
const eventListeners: InternalEventListeners = this._eventListeners || {};
if (this._eventListeners == null) {
this._eventListeners = eventListeners;
}

const namedEventListeners = eventListeners[eventType] || [];
if (eventListeners[eventType] == null) {
eventListeners[eventType] = namedEventListeners;
}

namedEventListeners.push({
listener: listener,
invalidated: false,
options: {
capture: capture,
once: once,
passive: passive,
signal: signal,
},
});
}

// See https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener
removeEventListener_unstable(
eventType: string,
listener: EventListener,
options: EventListenerRemoveOptions | boolean,
) {
// eventType and listener must be referentially equal to be removed from the listeners
// data structure, but in "options" we only check the `capture` flag, according to spec.
// That means if you add the same function as a listener with capture set to true and false,
// you must also call removeEventListener twice with capture set to true/false.
const optionsObj =
typeof options === 'object' && options !== null ? options : {};
const capture =
(typeof options === 'boolean' ? options : optionsObj.capture) || false;

// If there are no event listeners or named event listeners, we can bail early - our
// job is already done.
const eventListeners = this._eventListeners;
if (!eventListeners) {
return;
}
const namedEventListeners = eventListeners[eventType];
if (!namedEventListeners) {
return;
}

// TODO: optimize this path to make remove cheaper
eventListeners[eventType] = namedEventListeners.filter(listenerObj => {
return !(
listenerObj.listener === listener &&
listenerObj.options.capture === capture
);
});
}
}

// $FlowFixMe[class-object-subtyping] found when upgrading Flow
Expand Down
57 changes: 20 additions & 37 deletions packages/react-native-renderer/src/ReactNativeBridgeEventPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,13 @@ import type {
} from './legacy-events/PluginModuleType';
import type {TopLevelType} from './legacy-events/TopLevelEventTypes';
import SyntheticEvent from './legacy-events/SyntheticEvent';
import type {PropagationPhases} from './legacy-events/PropagationPhases';

// Module provided by RN:
import {ReactNativeViewConfigRegistry} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';
import accumulateInto from './legacy-events/accumulateInto';
import getListeners from './ReactNativeGetListeners';
import getListener from './ReactNativeGetListener';
import forEachAccumulated from './legacy-events/forEachAccumulated';
import {HostComponent} from 'react-reconciler/src/ReactWorkTags';
import isArray from 'shared/isArray';

const {customBubblingEventTypes, customDirectEventTypes} =
ReactNativeViewConfigRegistry;
Expand All @@ -30,38 +28,10 @@ const {customBubblingEventTypes, customDirectEventTypes} =
// EventPropagator.js, as they deviated from ReactDOM's newer
// implementations.
// $FlowFixMe[missing-local-annot]
function listenersAtPhase(inst, event, propagationPhase: PropagationPhases) {
function listenerAtPhase(inst, event, propagationPhase: PropagationPhases) {
const registrationName =
event.dispatchConfig.phasedRegistrationNames[propagationPhase];
return getListeners(inst, registrationName, propagationPhase, true);
}

// $FlowFixMe[missing-local-annot]
function accumulateListenersAndInstances(inst, event, listeners) {
const listenersLength = listeners
? isArray(listeners)
? listeners.length
: 1
: 0;
if (listenersLength > 0) {
event._dispatchListeners = accumulateInto(
event._dispatchListeners,
listeners,
);

// Avoid allocating additional arrays here
if (event._dispatchInstances == null && listenersLength === 1) {
event._dispatchInstances = inst;
} else {
event._dispatchInstances = event._dispatchInstances || [];
if (!isArray(event._dispatchInstances)) {
event._dispatchInstances = [event._dispatchInstances];
}
for (let i = 0; i < listenersLength; i++) {
event._dispatchInstances.push(inst);
}
}
}
return getListener(inst, registrationName);
}

// $FlowFixMe[missing-local-annot]
Expand All @@ -71,8 +41,14 @@ function accumulateDirectionalDispatches(inst, phase, event) {
console.error('Dispatching inst must not be null');
}
}
const listeners = listenersAtPhase(inst, event, phase);
accumulateListenersAndInstances(inst, event, listeners);
const listener = listenerAtPhase(inst, event, phase);
if (listener) {
event._dispatchListeners = accumulateInto(
event._dispatchListeners,
listener,
);
event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
}
}

// $FlowFixMe[missing-local-annot]
Expand Down Expand Up @@ -160,8 +136,14 @@ function accumulateDispatches(
): void {
if (inst && event && event.dispatchConfig.registrationName) {
const registrationName = event.dispatchConfig.registrationName;
const listeners = getListeners(inst, registrationName, 'bubbled', false);
accumulateListenersAndInstances(inst, event, listeners);
const listener = getListener(inst, registrationName);
if (listener) {
event._dispatchListeners = accumulateInto(
event._dispatchListeners,
listener,
);
event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
}
}
}

Expand All @@ -181,6 +163,7 @@ function accumulateDirectDispatches(events: ?(Array<Object> | Object)) {
}

// End of inline
type PropagationPhases = 'bubbled' | 'captured';

const ReactNativeBridgeEventPlugin = {
eventTypes: ({}: EventTypes),
Expand Down
4 changes: 2 additions & 2 deletions packages/react-native-renderer/src/ReactNativeEventEmitter.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ import {
} from './legacy-events/EventPluginRegistry';
import {batchedUpdates} from './legacy-events/ReactGenericBatching';
import {runEventsInBatch} from './legacy-events/EventBatching';
import getListeners from './ReactNativeGetListeners';
import getListener from './ReactNativeGetListener';
import accumulateInto from './legacy-events/accumulateInto';

import {getInstanceFromNode} from './ReactNativeComponentTree';

export {getListeners, registrationNameModules as registrationNames};
export {getListener, registrationNameModules as registrationNames};

/**
* Version of `ReactBrowserEventEmitter` that works on the receiving side of a
Expand Down
37 changes: 37 additions & 0 deletions packages/react-native-renderer/src/ReactNativeGetListener.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';

import {getFiberCurrentPropsFromNode} from './legacy-events/EventPluginUtils';

export default function getListener(
inst: Fiber,
registrationName: string,
): Function | null {
const stateNode = inst.stateNode;
if (stateNode === null) {
// Work in progress (ex: onload events in incremental mode).
return null;
}
const props = getFiberCurrentPropsFromNode(stateNode);
if (props === null) {
// Work in progress.
return null;
}
const listener = props[registrationName];

if (listener && typeof listener !== 'function') {
throw new Error(
`Expected \`${registrationName}\` listener to be a function, instead got a value of \`${typeof listener}\` type.`,
);
}

return listener;
}
Loading