Skip to content

Commit d39643b

Browse files
yungstersfacebook-github-bot
authored andcommitted
RN: Rewrite NativeEventEmitter
Summary: Rewrites `NativeEventEmitter` to not extend `EventEmitter` and to compose `RCTDeviceEventEmitter` instead of (mis)using its exported `sharedSubscriber` property. This makes it easier to reason about `NativeEventEmitter`. Also, the extraneous methods on `EventEmitter` are no longer inherited. Changelog: [General][Removed] - `NativeEventEmitter` no longer inherits from `EventEmitter`, so it no longer implements `removeListener` and `removeSubscription`. Instead, use the `remove()` method on the subscription object returned by `addListener`. Reviewed By: rubennorte Differential Revision: D26163562 fbshipit-source-id: c1aadb99bdefbaa36fece57ce74604e414f94d4d
1 parent 7d4612b commit d39643b

File tree

3 files changed

+105
-42
lines changed

3 files changed

+105
-42
lines changed

Libraries/EventEmitter/NativeEventEmitter.js

+65-36
Original file line numberDiff line numberDiff line change
@@ -4,67 +4,96 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7+
* @flow strict-local
78
* @format
8-
* @flow
99
*/
1010

1111
'use strict';
1212

13+
import {
14+
type EventSubscription,
15+
type IEventEmitter,
16+
} from '../vendor/emitter/EventEmitter';
1317
import Platform from '../Utilities/Platform';
14-
import EventEmitter from '../vendor/emitter/EventEmitter';
15-
import type EmitterSubscription from '../vendor/emitter/_EmitterSubscription';
1618
import RCTDeviceEventEmitter from './RCTDeviceEventEmitter';
1719
import invariant from 'invariant';
1820

19-
type NativeModule = {
20-
+addListener: (eventType: string) => void,
21-
+removeListeners: (count: number) => void,
21+
type NativeModule = $ReadOnly<{
22+
addListener: (eventType: string) => void,
23+
removeListeners: (count: number) => void,
2224
...
23-
};
25+
}>;
26+
27+
export type {EventSubscription};
2428

2529
/**
26-
* Abstract base class for implementing event-emitting modules. This implements
27-
* a subset of the standard EventEmitter node module API.
30+
* `NativeEventEmitter` is intended for use by Native Modules to emit events to
31+
* JavaScript listeners. If a `NativeModule` is supplied to the constructor, it
32+
* will be notified (via `addListener` and `removeListeners`) when the listener
33+
* count changes to manage "native memory".
34+
*
35+
* Currently, all native events are fired via a global `RCTDeviceEventEmitter`.
36+
* This means event names must be globally unique, and it means that call sites
37+
* can theoretically listen to `RCTDeviceEventEmitter` (although discouraged).
2838
*/
29-
export default class NativeEventEmitter<
30-
EventDefinitions: {...},
31-
> extends EventEmitter<EventDefinitions> {
39+
export default class NativeEventEmitter<TEventToArgsMap: {...}>
40+
implements IEventEmitter<TEventToArgsMap> {
3241
_nativeModule: ?NativeModule;
3342

3443
constructor(nativeModule: ?NativeModule) {
35-
super(RCTDeviceEventEmitter.sharedSubscriber);
3644
if (Platform.OS === 'ios') {
37-
invariant(nativeModule, 'Native module cannot be null.');
45+
invariant(
46+
nativeModule != null,
47+
'`new NativeEventEmitter()` requires a non-null argument.',
48+
);
3849
this._nativeModule = nativeModule;
3950
}
4051
}
4152

42-
addListener<K: $Keys<EventDefinitions>>(
43-
eventType: K,
44-
listener: (...$ElementType<EventDefinitions, K>) => mixed,
45-
context: $FlowFixMe,
46-
): EmitterSubscription<EventDefinitions, K> {
47-
if (this._nativeModule != null) {
48-
this._nativeModule.addListener(eventType);
49-
}
50-
return super.addListener(eventType, listener, context);
53+
addListener<TEvent: $Keys<TEventToArgsMap>>(
54+
eventType: TEvent,
55+
listener: (...args: $ElementType<TEventToArgsMap, TEvent>) => mixed,
56+
context?: mixed,
57+
): EventSubscription {
58+
this._nativeModule?.addListener(eventType);
59+
let subscription: ?EventSubscription = RCTDeviceEventEmitter.addListener(
60+
eventType,
61+
listener,
62+
context,
63+
);
64+
65+
return {
66+
remove: () => {
67+
if (subscription != null) {
68+
this._nativeModule?.removeListeners(1);
69+
subscription.remove();
70+
subscription = null;
71+
}
72+
},
73+
};
5174
}
5275

53-
removeAllListeners<K: $Keys<EventDefinitions>>(eventType: ?K): void {
54-
invariant(eventType, 'eventType argument is required.');
55-
const count = this.listenerCount(eventType);
56-
if (this._nativeModule != null) {
57-
this._nativeModule.removeListeners(count);
58-
}
59-
super.removeAllListeners(eventType);
76+
emit<TEvent: $Keys<TEventToArgsMap>>(
77+
eventType: TEvent,
78+
...args: $ElementType<TEventToArgsMap, TEvent>
79+
): void {
80+
// Generally, `RCTDeviceEventEmitter` is directly invoked. But this is
81+
// included for completeness.
82+
RCTDeviceEventEmitter.emit(eventType, ...args);
6083
}
6184

62-
removeSubscription<K: $Keys<EventDefinitions>>(
63-
subscription: EmitterSubscription<EventDefinitions, K>,
85+
removeAllListeners<TEvent: $Keys<TEventToArgsMap>>(
86+
eventType?: ?TEvent,
6487
): void {
65-
if (this._nativeModule != null) {
66-
this._nativeModule.removeListeners(1);
67-
}
68-
super.removeSubscription(subscription);
88+
invariant(
89+
eventType != null,
90+
'`NativeEventEmitter.removeAllListener()` requires a non-null argument.',
91+
);
92+
this._nativeModule?.removeListeners(this.listenerCount(eventType));
93+
RCTDeviceEventEmitter.removeAllListeners(eventType);
94+
}
95+
96+
listenerCount<TEvent: $Keys<TEventToArgsMap>>(eventType: TEvent): number {
97+
return RCTDeviceEventEmitter.listenerCount(eventType);
6998
}
7099
}

Libraries/Utilities/DevSettings.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -29,27 +29,27 @@ if (__DEV__) {
2929
const emitter = new NativeEventEmitter<DevSettingsEventDefinitions>(
3030
NativeDevSettings,
3131
);
32-
const menuItems = new Map();
32+
const subscriptions = new Map();
3333

3434
DevSettings = {
3535
addMenuItem(title: string, handler: () => mixed): void {
3636
// Make sure items are not added multiple times. This can
3737
// happen when hot reloading the module that registers the
3838
// menu items. The title is used as the id which means we
3939
// don't support multiple items with the same name.
40-
const oldHandler = menuItems.get(title);
41-
if (oldHandler != null) {
42-
emitter.removeListener('didPressMenuItem', oldHandler);
40+
let subscription = subscriptions.get(title);
41+
if (subscription != null) {
42+
subscription.remove();
4343
} else {
4444
NativeDevSettings.addMenuItem(title);
4545
}
4646

47-
menuItems.set(title, handler);
48-
emitter.addListener('didPressMenuItem', event => {
47+
subscription = emitter.addListener('didPressMenuItem', event => {
4948
if (event.title === title) {
5049
handler();
5150
}
5251
});
52+
subscriptions.set(title, subscription);
5353
},
5454
reload(reason?: string): void {
5555
if (NativeDevSettings.reloadWithReason != null) {

Libraries/vendor/emitter/EventEmitter.js

+34
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,37 @@ import type {EventSubscription} from './EventSubscription';
1717
export default EventEmitter;
1818

1919
export type {EventSubscription};
20+
21+
/**
22+
* Essential interface for an EventEmitter.
23+
*/
24+
export interface IEventEmitter<TEventToArgsMap: {...}> {
25+
/**
26+
* Registers a listener that is called when the supplied event is emitted.
27+
* Returns a subscription that has a `remove` method to undo registration.
28+
*/
29+
addListener<TEvent: $Keys<TEventToArgsMap>>(
30+
eventType: TEvent,
31+
listener: (...args: $ElementType<TEventToArgsMap, TEvent>) => void,
32+
context?: mixed,
33+
): EventSubscription;
34+
35+
/**
36+
* Emits the supplied event. Additional arguments supplied to `emit` will be
37+
* passed through to each of the registered listeners.
38+
*/
39+
emit<TEvent: $Keys<TEventToArgsMap>>(
40+
eventType: TEvent,
41+
...args: $ElementType<TEventToArgsMap, TEvent>
42+
): void;
43+
44+
/**
45+
* Removes all registered listeners.
46+
*/
47+
removeAllListeners<TEvent: $Keys<TEventToArgsMap>>(eventType?: ?TEvent): void;
48+
49+
/**
50+
* Returns the number of registered listeners for the supplied event.
51+
*/
52+
listenerCount<TEvent: $Keys<TEventToArgsMap>>(eventType: TEvent): number;
53+
}

0 commit comments

Comments
 (0)