Skip to content

Commit 543353a

Browse files
authored
Experimental Event API: Remove "listener" from event objects (#15391)
1 parent ed67984 commit 543353a

File tree

8 files changed

+139
-125
lines changed

8 files changed

+139
-125
lines changed

packages/react-dom/src/events/DOMEventResponderSystem.js

Lines changed: 101 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import type {Fiber} from 'react-reconciler/src/ReactFiber';
3030
import warning from 'shared/warning';
3131
import {enableEventAPI} from 'shared/ReactFeatureFlags';
3232
import {invokeGuardedCallbackAndCatchFirstError} from 'shared/ReactErrorUtils';
33+
import invariant from 'shared/invariant';
3334

3435
import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree';
3536

@@ -50,7 +51,6 @@ type EventQueue = {
5051
};
5152

5253
type PartialEventObject = {
53-
listener: ($Shape<PartialEventObject>) => void,
5454
target: Element | Document,
5555
type: string,
5656
};
@@ -76,22 +76,31 @@ const targetEventTypeCached: Map<
7676
Set<DOMTopLevelEventType>,
7777
> = new Map();
7878
const ownershipChangeListeners: Set<ReactEventComponentInstance> = new Set();
79+
const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
80+
const eventListeners:
81+
| WeakMap
82+
| Map<
83+
$Shape<PartialEventObject>,
84+
($Shape<PartialEventObject>) => void,
85+
> = new PossiblyWeakMap();
7986

8087
let currentTimers = new Map();
8188
let currentOwner = null;
82-
let currentInstance: ReactEventComponentInstance;
83-
let currentEventQueue: EventQueue;
89+
let currentInstance: null | ReactEventComponentInstance = null;
90+
let currentEventQueue: null | EventQueue = null;
8491

8592
const eventResponderContext: ReactResponderContext = {
8693
dispatchEvent(
8794
possibleEventObject: Object,
95+
listener: ($Shape<PartialEventObject>) => void,
8896
{capture, discrete}: ReactResponderDispatchEventOptions,
8997
): void {
90-
const {listener, target, type} = possibleEventObject;
98+
validateResponderContext();
99+
const {target, type} = possibleEventObject;
91100

92-
if (listener == null || target == null || type == null) {
101+
if (target == null || type == null) {
93102
throw new Error(
94-
'context.dispatchEvent: "listener", "target" and "type" fields on event object are required.',
103+
'context.dispatchEvent: "target" and "type" fields on event object are required.',
95104
);
96105
}
97106
if (__DEV__) {
@@ -115,15 +124,18 @@ const eventResponderContext: ReactResponderContext = {
115124
>);
116125
const events = getEventsFromEventQueue(capture);
117126
if (discrete) {
118-
currentEventQueue.discrete = true;
127+
((currentEventQueue: any): EventQueue).discrete = true;
119128
}
129+
eventListeners.set(eventObject, listener);
120130
events.push(eventObject);
121131
},
122132
dispatchStopPropagation(capture?: boolean) {
133+
validateResponderContext();
123134
const events = getEventsFromEventQueue();
124135
events.push({stopPropagation: true});
125136
},
126137
isPositionWithinTouchHitTarget(doc: Document, x: number, y: number): boolean {
138+
validateResponderContext();
127139
// This isn't available in some environments (JSDOM)
128140
if (typeof doc.elementFromPoint !== 'function') {
129141
return false;
@@ -151,6 +163,7 @@ const eventResponderContext: ReactResponderContext = {
151163
return false;
152164
},
153165
isTargetWithinEventComponent(target: Element | Document): boolean {
166+
validateResponderContext();
154167
if (target != null) {
155168
let fiber = getClosestInstanceFromNode(target);
156169
while (fiber !== null) {
@@ -182,6 +195,7 @@ const eventResponderContext: ReactResponderContext = {
182195
doc: Document,
183196
rootEventTypes: Array<ReactEventResponderEventType>,
184197
): void {
198+
validateResponderContext();
185199
listenToResponderEventTypesImpl(rootEventTypes, doc);
186200
for (let i = 0; i < rootEventTypes.length; i++) {
187201
const rootEventType = rootEventTypes[i];
@@ -197,12 +211,15 @@ const eventResponderContext: ReactResponderContext = {
197211
rootEventComponentInstances,
198212
);
199213
}
200-
rootEventComponentInstances.add(currentInstance);
214+
rootEventComponentInstances.add(
215+
((currentInstance: any): ReactEventComponentInstance),
216+
);
201217
}
202218
},
203219
removeRootEventTypes(
204220
rootEventTypes: Array<ReactEventResponderEventType>,
205221
): void {
222+
validateResponderContext();
206223
for (let i = 0; i < rootEventTypes.length; i++) {
207224
const rootEventType = rootEventTypes[i];
208225
const topLevelEventType =
@@ -211,14 +228,18 @@ const eventResponderContext: ReactResponderContext = {
211228
topLevelEventType,
212229
);
213230
if (rootEventComponents !== undefined) {
214-
rootEventComponents.delete(currentInstance);
231+
rootEventComponents.delete(
232+
((currentInstance: any): ReactEventComponentInstance),
233+
);
215234
}
216235
}
217236
},
218237
hasOwnership(): boolean {
238+
validateResponderContext();
219239
return currentOwner === currentInstance;
220240
},
221241
requestOwnership(): boolean {
242+
validateResponderContext();
222243
if (currentOwner !== null) {
223244
return false;
224245
}
@@ -227,6 +248,7 @@ const eventResponderContext: ReactResponderContext = {
227248
return true;
228249
},
229250
releaseOwnership(): boolean {
251+
validateResponderContext();
230252
if (currentOwner !== currentInstance) {
231253
return false;
232254
}
@@ -235,6 +257,7 @@ const eventResponderContext: ReactResponderContext = {
235257
return false;
236258
},
237259
setTimeout(func: () => void, delay): Symbol {
260+
validateResponderContext();
238261
if (currentTimers === null) {
239262
currentTimers = new Map();
240263
}
@@ -253,14 +276,15 @@ const eventResponderContext: ReactResponderContext = {
253276
currentTimers.set(delay, timeout);
254277
}
255278
timeout.timers.set(timerId, {
256-
instance: currentInstance,
279+
instance: ((currentInstance: any): ReactEventComponentInstance),
257280
func,
258281
id: timerId,
259282
});
260283
activeTimeouts.set(timerId, timeout);
261284
return timerId;
262285
},
263286
clearTimeout(timerId: Symbol): void {
287+
validateResponderContext();
264288
const timeout = activeTimeouts.get(timerId);
265289

266290
if (timeout !== undefined) {
@@ -279,6 +303,7 @@ const eventResponderContext: ReactResponderContext = {
279303
node: Element,
280304
props: null | Object,
281305
}> {
306+
validateResponderContext();
282307
const eventTargetHostComponents = [];
283308
let node = getClosestInstanceFromNode(target);
284309
// We traverse up the fiber tree from the target fiber, to the
@@ -326,28 +351,26 @@ const eventResponderContext: ReactResponderContext = {
326351
};
327352

328353
function getEventsFromEventQueue(capture?: boolean): Array<EventObjectTypes> {
354+
const eventQueue = ((currentEventQueue: any): EventQueue);
329355
let events;
330356
if (capture) {
331-
events = currentEventQueue.capture;
357+
events = eventQueue.capture;
332358
if (events === null) {
333-
events = currentEventQueue.capture = [];
359+
events = eventQueue.capture = [];
334360
}
335361
} else {
336-
events = currentEventQueue.bubble;
362+
events = eventQueue.bubble;
337363
if (events === null) {
338-
events = currentEventQueue.bubble = [];
364+
events = eventQueue.bubble = [];
339365
}
340366
}
341367
return events;
342368
}
343369

344370
function processTimers(timers: Map<Symbol, ResponderTimer>): void {
345-
const previousEventQueue = currentEventQueue;
346-
const previousInstance = currentInstance;
371+
const timersArr = Array.from(timers.values());
347372
currentEventQueue = createEventQueue();
348-
349373
try {
350-
const timersArr = Array.from(timers.values());
351374
for (let i = 0; i < timersArr.length; i++) {
352375
const {instance, func, id} = timersArr[i];
353376
currentInstance = instance;
@@ -359,9 +382,9 @@ function processTimers(timers: Map<Symbol, ResponderTimer>): void {
359382
}
360383
batchedUpdates(processEventQueue, currentEventQueue);
361384
} finally {
362-
currentInstance = previousInstance;
363-
currentEventQueue = previousEventQueue;
364385
currentTimers = null;
386+
currentInstance = null;
387+
currentEventQueue = null;
365388
}
366389
}
367390

@@ -404,7 +427,9 @@ function createEventQueue(): EventQueue {
404427

405428
function processEvent(event: $Shape<PartialEventObject>): void {
406429
const type = event.type;
407-
const listener = event.listener;
430+
const listener = ((eventListeners.get(event): any): (
431+
$Shape<PartialEventObject>,
432+
) => void);
408433
invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event);
409434
}
410435

@@ -435,7 +460,7 @@ function processEvents(
435460
}
436461

437462
export function processEventQueue(): void {
438-
const {bubble, capture, discrete} = currentEventQueue;
463+
const {bubble, capture, discrete} = ((currentEventQueue: any): EventQueue);
439464

440465
if (discrete) {
441466
interactiveUpdates(() => {
@@ -478,13 +503,8 @@ function handleTopLevelType(
478503
return;
479504
}
480505
}
481-
const previousInstance = currentInstance;
482506
currentInstance = eventComponentInstance;
483-
try {
484-
responder.onEvent(responderEvent, eventResponderContext, props, state);
485-
} finally {
486-
currentInstance = previousInstance;
487-
}
507+
responder.onEvent(responderEvent, eventResponderContext, props, state);
488508
}
489509

490510
export function runResponderEventsInBatch(
@@ -502,54 +522,60 @@ export function runResponderEventsInBatch(
502522
((nativeEventTarget: any): Element | Document),
503523
eventSystemFlags,
504524
);
505-
let node = targetFiber;
506-
// Traverse up the fiber tree till we find event component fibers.
507-
while (node !== null) {
508-
if (node.tag === EventComponent) {
509-
const eventComponentInstance = node.stateNode;
510-
handleTopLevelType(
511-
topLevelType,
512-
responderEvent,
513-
eventComponentInstance,
514-
false,
515-
);
525+
526+
try {
527+
let node = targetFiber;
528+
// Traverse up the fiber tree till we find event component fibers.
529+
while (node !== null) {
530+
if (node.tag === EventComponent) {
531+
const eventComponentInstance = node.stateNode;
532+
handleTopLevelType(
533+
topLevelType,
534+
responderEvent,
535+
eventComponentInstance,
536+
false,
537+
);
538+
}
539+
node = node.return;
516540
}
517-
node = node.return;
518-
}
519-
// Handle root level events
520-
const rootEventInstances = rootEventTypesToEventComponentInstances.get(
521-
topLevelType,
522-
);
523-
if (rootEventInstances !== undefined) {
524-
const rootEventComponentInstances = Array.from(rootEventInstances);
525-
526-
for (let i = 0; i < rootEventComponentInstances.length; i++) {
527-
const rootEventComponentInstance = rootEventComponentInstances[i];
528-
handleTopLevelType(
529-
topLevelType,
530-
responderEvent,
531-
rootEventComponentInstance,
532-
true,
533-
);
541+
// Handle root level events
542+
const rootEventInstances = rootEventTypesToEventComponentInstances.get(
543+
topLevelType,
544+
);
545+
if (rootEventInstances !== undefined) {
546+
const rootEventComponentInstances = Array.from(rootEventInstances);
547+
548+
for (let i = 0; i < rootEventComponentInstances.length; i++) {
549+
const rootEventComponentInstance = rootEventComponentInstances[i];
550+
handleTopLevelType(
551+
topLevelType,
552+
responderEvent,
553+
rootEventComponentInstance,
554+
true,
555+
);
556+
}
534557
}
558+
processEventQueue();
559+
} finally {
560+
currentTimers = null;
561+
currentInstance = null;
562+
currentEventQueue = null;
535563
}
536-
processEventQueue();
537-
currentTimers = null;
538564
}
539565
}
540566

541567
function triggerOwnershipListeners(): void {
542568
const listeningInstances = Array.from(ownershipChangeListeners);
543569
const previousInstance = currentInstance;
544-
for (let i = 0; i < listeningInstances.length; i++) {
545-
const instance = listeningInstances[i];
546-
const {props, responder, state} = instance;
547-
currentInstance = instance;
548-
try {
570+
try {
571+
for (let i = 0; i < listeningInstances.length; i++) {
572+
const instance = listeningInstances[i];
573+
const {props, responder, state} = instance;
574+
currentInstance = instance;
549575
responder.onOwnershipChange(eventResponderContext, props, state);
550-
} finally {
551-
currentInstance = previousInstance;
552576
}
577+
} finally {
578+
currentInstance = previousInstance;
553579
}
554580
}
555581

@@ -569,15 +595,13 @@ export function unmountEventResponder(
569595
const onUnmount = responder.onUnmount;
570596
if (onUnmount !== undefined) {
571597
let {props, state} = eventComponentInstance;
572-
const previousEventQueue = currentEventQueue;
573-
const previousInstance = currentInstance;
574598
currentEventQueue = createEventQueue();
575599
currentInstance = eventComponentInstance;
576600
try {
577601
onUnmount(eventResponderContext, props, state);
578602
} finally {
579-
currentEventQueue = previousEventQueue;
580-
currentInstance = previousInstance;
603+
currentEventQueue = null;
604+
currentInstance = null;
581605
currentTimers = null;
582606
}
583607
}
@@ -589,3 +613,11 @@ export function unmountEventResponder(
589613
ownershipChangeListeners.delete(eventComponentInstance);
590614
}
591615
}
616+
617+
function validateResponderContext(): void {
618+
invariant(
619+
currentEventQueue && currentInstance,
620+
'An event responder context was used outside of an event cycle. ' +
621+
'Use context.setTimeout() to use asynchronous responder context outside of event cycle .',
622+
);
623+
}

0 commit comments

Comments
 (0)