From 7dbfc1099c2e569cd811d01454a2d8b7716ad06d Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Tue, 11 Feb 2025 05:42:24 -0800 Subject: [PATCH] Align EventEmitter definitions with TypeScript (#49206) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/49206 Changelog: [Internal] Reviewed By: huntie Differential Revision: D68953500 fbshipit-source-id: 0e936e8423f8792db34d6b44b1620dde5bd13a47 --- .../EventEmitter/NativeEventEmitter.js | 10 +++++-- .../__mocks__/NativeEventEmitter.js | 5 ++-- .../__snapshots__/public-api-test.js.snap | 22 ++++++++++---- .../Libraries/vendor/emitter/EventEmitter.js | 30 +++++++++++++------ scripts/build/build-types/buildTypes.js | 1 + 5 files changed, 49 insertions(+), 19 deletions(-) diff --git a/packages/react-native/Libraries/EventEmitter/NativeEventEmitter.js b/packages/react-native/Libraries/EventEmitter/NativeEventEmitter.js index f30a69df7a4..28b19aaf607 100644 --- a/packages/react-native/Libraries/EventEmitter/NativeEventEmitter.js +++ b/packages/react-native/Libraries/EventEmitter/NativeEventEmitter.js @@ -26,6 +26,9 @@ interface NativeModule { export type {EventSubscription}; +// $FlowFixMe[unclear-type] unclear type of events +type UnsafeObject = Object; + /** * `NativeEventEmitter` is intended for use by Native Modules to emit events to * JavaScript listeners. If a `NativeModule` is supplied to the constructor, it @@ -36,8 +39,11 @@ export type {EventSubscription}; * This means event names must be globally unique, and it means that call sites * can theoretically listen to `RCTDeviceEventEmitter` (although discouraged). */ -export default class NativeEventEmitter - implements IEventEmitter +export default class NativeEventEmitter< + TEventToArgsMap: $ReadOnly< + Record>, + > = $ReadOnly>>, +> implements IEventEmitter { _nativeModule: ?NativeModule; diff --git a/packages/react-native/Libraries/EventEmitter/__mocks__/NativeEventEmitter.js b/packages/react-native/Libraries/EventEmitter/__mocks__/NativeEventEmitter.js index 1a63d5a88a4..1b6196cb93f 100644 --- a/packages/react-native/Libraries/EventEmitter/__mocks__/NativeEventEmitter.js +++ b/packages/react-native/Libraries/EventEmitter/__mocks__/NativeEventEmitter.js @@ -18,8 +18,9 @@ import RCTDeviceEventEmitter from '../RCTDeviceEventEmitter'; /** * Mock `NativeEventEmitter` to ignore Native Modules. */ -export default class NativeEventEmitter - implements IEventEmitter +export default class NativeEventEmitter< + TEventToArgsMap: $ReadOnly>>, +> implements IEventEmitter { addListener>( eventType: TEvent, diff --git a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap index 639455ea178..6dcba65056d 100644 --- a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap +++ b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap @@ -4198,8 +4198,12 @@ exports[`public API should not change unintentionally Libraries/EventEmitter/Nat removeListeners(count: number): void; } export type { EventSubscription }; -declare export default class NativeEventEmitter - implements IEventEmitter +type UnsafeObject = Object; +declare export default class NativeEventEmitter< + TEventToArgsMap: $ReadOnly< + Record>, + > = $ReadOnly>>, +> implements IEventEmitter { constructor(nativeModule: ?NativeModule): void; addListener>( @@ -8801,10 +8805,13 @@ exports[`public API should not change unintentionally Libraries/vendor/core/Erro `; exports[`public API should not change unintentionally Libraries/vendor/emitter/EventEmitter.js 1`] = ` -"export interface EventSubscription { +"type UnsafeObject = Object; +export interface EventSubscription { remove(): void; } -export interface IEventEmitter { +export interface IEventEmitter< + TEventToArgsMap: $ReadOnly>>, +> { addListener>( eventType: TEvent, listener: (...args: TEventToArgsMap[TEvent]) => mixed, @@ -8817,8 +8824,11 @@ export interface IEventEmitter { removeAllListeners>(eventType?: ?TEvent): void; listenerCount>(eventType: TEvent): number; } -declare export default class EventEmitter - implements IEventEmitter +declare export default class EventEmitter< + TEventToArgsMap: $ReadOnly< + Record>, + > = $ReadOnly>>, +> implements IEventEmitter { addListener>( eventType: TEvent, diff --git a/packages/react-native/Libraries/vendor/emitter/EventEmitter.js b/packages/react-native/Libraries/vendor/emitter/EventEmitter.js index 1087654ea57..016bff45a41 100644 --- a/packages/react-native/Libraries/vendor/emitter/EventEmitter.js +++ b/packages/react-native/Libraries/vendor/emitter/EventEmitter.js @@ -8,11 +8,16 @@ * @format */ +// $FlowFixMe[unclear-type] unclear type of events +type UnsafeObject = Object; + export interface EventSubscription { remove(): void; } -export interface IEventEmitter { +export interface IEventEmitter< + TEventToArgsMap: $ReadOnly>>, +> { addListener>( eventType: TEvent, listener: (...args: TEventToArgsMap[TEvent]) => mixed, @@ -35,7 +40,9 @@ interface Registration { +remove: () => void; } -type Registry = { +type Registry< + TEventToArgsMap: $ReadOnly>>, +> = { [K in keyof TEventToArgsMap]: Set>, }; @@ -59,8 +66,11 @@ type Registry = { * emitter.emit('error', new Error('Resource not found')); * */ -export default class EventEmitter - implements IEventEmitter +export default class EventEmitter< + TEventToArgsMap: $ReadOnly< + Record>, + > = $ReadOnly>>, +> implements IEventEmitter { // $FlowFixMe[incompatible-type] #registry: Registry = {}; @@ -81,7 +91,7 @@ export default class EventEmitter } const registrations = allocate< TEventToArgsMap, - TEvent, + $Keys, TEventToArgsMap[TEvent], >(this.#registry, eventType); const registration: Registration = { @@ -136,20 +146,22 @@ export default class EventEmitter * Returns the number of registered listeners for the supplied event. */ listenerCount>(eventType: TEvent): number { - const registrations: ?Set> = this.#registry[eventType]; + const registrations: ?Set> = + this.#registry[eventType]; return registrations == null ? 0 : registrations.size; } } function allocate< - TEventToArgsMap: {...}, + TEventToArgsMap: $ReadOnly>>, TEvent: $Keys, TEventArgs: TEventToArgsMap[TEvent], >( registry: Registry, eventType: TEvent, -): Set> { - let registrations: ?Set> = registry[eventType]; +): Set> { + let registrations: ?Set> = + registry[eventType]; if (registrations == null) { registrations = new Set(); registry[eventType] = registrations; diff --git a/scripts/build/build-types/buildTypes.js b/scripts/build/build-types/buildTypes.js index ddc5ddf2563..405beca664e 100644 --- a/scripts/build/build-types/buildTypes.js +++ b/scripts/build/build-types/buildTypes.js @@ -33,6 +33,7 @@ const ENTRY_POINTS = [ 'packages/react-native/Libraries/Settings/Settings.js', 'packages/react-native/Libraries/Performance/Systrace.js', 'packages/react-native/Libraries/LogBox/LogBox.js', + 'packages/react-native/Libraries/vendor/emitter/EventEmitter.js', ]; /**