@samitouri / QOS-React / commits / 543353a043

Experimental Event API: Remove "listener" from event objects (#15391)

Dominic Gannaway committed Apr 12, 2019 at 11:53 UTC 543353a043a9f2ffe4d3e8b20383c05877d0a87e
8 files changed +139 -125
packages/react-dom/src/events/DOMEventResponderSystem.js
+101 -69
@@ -30,6 +30,7 @@ import type {Fiber} from 'react-reconciler/src/ReactFiber';
30 import warning from 'shared/warning';
31 import {enableEventAPI} from 'shared/ReactFeatureFlags';
32 import {invokeGuardedCallbackAndCatchFirstError} from 'shared/ReactErrorUtils';
33 +import invariant from 'shared/invariant';
34
35 import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree';
36
@@ -50,7 +51,6 @@ type EventQueue = {
51 };
52
53 type PartialEventObject = {
53 - listener: ($Shape<PartialEventObject>) => void,
54 target: Element | Document,
55 type: string,
56 };
@@ -76,22 +76,31 @@ const targetEventTypeCached: Map<
76 Set<DOMTopLevelEventType>,
77 > = new Map();
78 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();
86
87 let currentTimers = new Map();
88 let currentOwner = null;
82 -let currentInstance: ReactEventComponentInstance;
83 -let currentEventQueue: EventQueue;
89 +let currentInstance: null | ReactEventComponentInstance = null;
90 +let currentEventQueue: null | EventQueue = null;
91
92 const eventResponderContext: ReactResponderContext = {
93 dispatchEvent(
94 possibleEventObject: Object,
95 + listener: ($Shape<PartialEventObject>) => void,
96 {capture, discrete}: ReactResponderDispatchEventOptions,
97 ): void {
90 - const {listener, target, type} = possibleEventObject;
98 + validateResponderContext();
99 + const {target, type} = possibleEventObject;
100
92 - if (listener == null || target == null || type == null) {
101 + if (target == null || type == null) {
102 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.',
104 );
105 }
106 if (__DEV__) {
@@ -115,15 +124,18 @@ const eventResponderContext: ReactResponderContext = {
124 >);
125 const events = getEventsFromEventQueue(capture);
126 if (discrete) {
118 - currentEventQueue.discrete = true;
127 + ((currentEventQueue: any): EventQueue).discrete = true;
128 }
129 + eventListeners.set(eventObject, listener);
130 events.push(eventObject);
131 },
132 dispatchStopPropagation(capture?: boolean) {
133 + validateResponderContext();
134 const events = getEventsFromEventQueue();
135 events.push({stopPropagation: true});
136 },
137 isPositionWithinTouchHitTarget(doc: Document, x: number, y: number): boolean {
138 + validateResponderContext();
139 // This isn't available in some environments (JSDOM)
140 if (typeof doc.elementFromPoint !== 'function') {
141 return false;
@@ -151,6 +163,7 @@ const eventResponderContext: ReactResponderContext = {
163 return false;
164 },
165 isTargetWithinEventComponent(target: Element | Document): boolean {
166 + validateResponderContext();
167 if (target != null) {
168 let fiber = getClosestInstanceFromNode(target);
169 while (fiber !== null) {
@@ -182,6 +195,7 @@ const eventResponderContext: ReactResponderContext = {
195 doc: Document,
196 rootEventTypes: Array<ReactEventResponderEventType>,
197 ): void {
198 + validateResponderContext();
199 listenToResponderEventTypesImpl(rootEventTypes, doc);
200 for (let i = 0; i < rootEventTypes.length; i++) {
201 const rootEventType = rootEventTypes[i];
@@ -197,12 +211,15 @@ const eventResponderContext: ReactResponderContext = {
211 rootEventComponentInstances,
212 );
213 }
200 - rootEventComponentInstances.add(currentInstance);
214 + rootEventComponentInstances.add(
215 + ((currentInstance: any): ReactEventComponentInstance),
216 + );
217 }
218 },
219 removeRootEventTypes(
220 rootEventTypes: Array<ReactEventResponderEventType>,
221 ): void {
222 + validateResponderContext();
223 for (let i = 0; i < rootEventTypes.length; i++) {
224 const rootEventType = rootEventTypes[i];
225 const topLevelEventType =
@@ -211,14 +228,18 @@ const eventResponderContext: ReactResponderContext = {
228 topLevelEventType,
229 );
230 if (rootEventComponents !== undefined) {
214 - rootEventComponents.delete(currentInstance);
231 + rootEventComponents.delete(
232 + ((currentInstance: any): ReactEventComponentInstance),
233 + );
234 }
235 }
236 },
237 hasOwnership(): boolean {
238 + validateResponderContext();
239 return currentOwner === currentInstance;
240 },
241 requestOwnership(): boolean {
242 + validateResponderContext();
243 if (currentOwner !== null) {
244 return false;
245 }
@@ -227,6 +248,7 @@ const eventResponderContext: ReactResponderContext = {
248 return true;
249 },
250 releaseOwnership(): boolean {
251 + validateResponderContext();
252 if (currentOwner !== currentInstance) {
253 return false;
254 }
@@ -235,6 +257,7 @@ const eventResponderContext: ReactResponderContext = {
257 return false;
258 },
259 setTimeout(func: () => void, delay): Symbol {
260 + validateResponderContext();
261 if (currentTimers === null) {
262 currentTimers = new Map();
263 }
@@ -253,7 +276,7 @@ const eventResponderContext: ReactResponderContext = {
276 currentTimers.set(delay, timeout);
277 }
278 timeout.timers.set(timerId, {
256 - instance: currentInstance,
279 + instance: ((currentInstance: any): ReactEventComponentInstance),
280 func,
281 id: timerId,
282 });
@@ -261,6 +284,7 @@ const eventResponderContext: ReactResponderContext = {
284 return timerId;
285 },
286 clearTimeout(timerId: Symbol): void {
287 + validateResponderContext();
288 const timeout = activeTimeouts.get(timerId);
289
290 if (timeout !== undefined) {
@@ -279,6 +303,7 @@ const eventResponderContext: ReactResponderContext = {
303 node: Element,
304 props: null | Object,
305 }> {
306 + validateResponderContext();
307 const eventTargetHostComponents = [];
308 let node = getClosestInstanceFromNode(target);
309 // We traverse up the fiber tree from the target fiber, to the
@@ -326,28 +351,26 @@ const eventResponderContext: ReactResponderContext = {
351 };
352
353 function getEventsFromEventQueue(capture?: boolean): Array<EventObjectTypes> {
354 + const eventQueue = ((currentEventQueue: any): EventQueue);
355 let events;
356 if (capture) {
331 - events = currentEventQueue.capture;
357 + events = eventQueue.capture;
358 if (events === null) {
333 - events = currentEventQueue.capture = [];
359 + events = eventQueue.capture = [];
360 }
361 } else {
336 - events = currentEventQueue.bubble;
362 + events = eventQueue.bubble;
363 if (events === null) {
338 - events = currentEventQueue.bubble = [];
364 + events = eventQueue.bubble = [];
365 }
366 }
367 return events;
368 }
369
370 function processTimers(timers: Map<Symbol, ResponderTimer>): void {
345 - const previousEventQueue = currentEventQueue;
346 - const previousInstance = currentInstance;
371 + const timersArr = Array.from(timers.values());
372 currentEventQueue = createEventQueue();
348 -
373 try {
350 - const timersArr = Array.from(timers.values());
374 for (let i = 0; i < timersArr.length; i++) {
375 const {instance, func, id} = timersArr[i];
376 currentInstance = instance;
@@ -359,9 +382,9 @@ function processTimers(timers: Map<Symbol, ResponderTimer>): void {
382 }
383 batchedUpdates(processEventQueue, currentEventQueue);
384 } finally {
362 - currentInstance = previousInstance;
363 - currentEventQueue = previousEventQueue;
385 currentTimers = null;
386 + currentInstance = null;
387 + currentEventQueue = null;
388 }
389 }
390
@@ -404,7 +427,9 @@ function createEventQueue(): EventQueue {
427
428 function processEvent(event: $Shape<PartialEventObject>): void {
429 const type = event.type;
407 - const listener = event.listener;
430 + const listener = ((eventListeners.get(event): any): (
431 + $Shape<PartialEventObject>,
432 + ) => void);
433 invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event);
434 }
435
@@ -435,7 +460,7 @@ function processEvents(
460 }
461
462 export function processEventQueue(): void {
438 - const {bubble, capture, discrete} = currentEventQueue;
463 + const {bubble, capture, discrete} = ((currentEventQueue: any): EventQueue);
464
465 if (discrete) {
466 interactiveUpdates(() => {
@@ -478,13 +503,8 @@ function handleTopLevelType(
503 return;
504 }
505 }
481 - const previousInstance = currentInstance;
506 currentInstance = eventComponentInstance;
483 - try {
484 - responder.onEvent(responderEvent, eventResponderContext, props, state);
485 - } finally {
486 - currentInstance = previousInstance;
487 - }
507 + responder.onEvent(responderEvent, eventResponderContext, props, state);
508 }
509
510 export function runResponderEventsInBatch(
@@ -502,54 +522,60 @@ export function runResponderEventsInBatch(
522 ((nativeEventTarget: any): Element | Document),
523 eventSystemFlags,
524 );
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;
540 }
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 + }
557 }
558 + processEventQueue();
559 + } finally {
560 + currentTimers = null;
561 + currentInstance = null;
562 + currentEventQueue = null;
563 }
536 - processEventQueue();
537 - currentTimers = null;
564 }
565 }
566
567 function triggerOwnershipListeners(): void {
568 const listeningInstances = Array.from(ownershipChangeListeners);
569 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;
575 responder.onOwnershipChange(eventResponderContext, props, state);
550 - } finally {
551 - currentInstance = previousInstance;
576 }
577 + } finally {
578 + currentInstance = previousInstance;
579 }
580 }
581
@@ -569,15 +595,13 @@ export function unmountEventResponder(
595 const onUnmount = responder.onUnmount;
596 if (onUnmount !== undefined) {
597 let {props, state} = eventComponentInstance;
572 - const previousEventQueue = currentEventQueue;
573 - const previousInstance = currentInstance;
598 currentEventQueue = createEventQueue();
599 currentInstance = eventComponentInstance;
600 try {
601 onUnmount(eventResponderContext, props, state);
602 } finally {
579 - currentEventQueue = previousEventQueue;
580 - currentInstance = previousInstance;
603 + currentEventQueue = null;
604 + currentInstance = null;
605 currentTimers = null;
606 }
607 }
@@ -589,3 +613,11 @@ export function unmountEventResponder(
613 ownershipChangeListeners.delete(eventComponentInstance);
614 }
615 }
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 +}
packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js
+12 -8
@@ -255,11 +255,12 @@ describe('DOMEventResponderSystem', () => {
255 (event, context, props) => {
256 if (props.onMagicClick) {
257 const syntheticEvent = {
258 - listener: props.onMagicClick,
258 target: event.target,
259 type: 'magicclick',
260 };
262 - context.dispatchEvent(syntheticEvent, {discrete: true});
261 + context.dispatchEvent(syntheticEvent, props.onMagicClick, {
262 + discrete: true,
263 + });
264 }
265 },
266 );
@@ -292,29 +293,32 @@ describe('DOMEventResponderSystem', () => {
293 undefined,
294 (event, context, props) => {
295 const pressEvent = {
295 - listener: props.onPress,
296 target: event.target,
297 type: 'press',
298 };
299 - context.dispatchEvent(pressEvent, {discrete: true});
299 + context.dispatchEvent(pressEvent, props.onPress, {discrete: true});
300
301 context.setTimeout(() => {
302 if (props.onLongPress) {
303 const longPressEvent = {
304 - listener: props.onLongPress,
304 target: event.target,
305 type: 'longpress',
306 };
308 - context.dispatchEvent(longPressEvent, {discrete: true});
307 + context.dispatchEvent(longPressEvent, props.onLongPress, {
308 + discrete: true,
309 + });
310 }
311
312 if (props.onLongPressChange) {
313 const longPressChangeEvent = {
313 - listener: props.onLongPressChange,
314 target: event.target,
315 type: 'longpresschange',
316 };
317 - context.dispatchEvent(longPressChangeEvent, {discrete: true});
317 + context.dispatchEvent(
318 + longPressChangeEvent,
319 + props.onLongPressChange,
320 + {discrete: true},
321 + );
322 }
323 }, 500);
324 },
packages/react-events/src/Drag.js
+2 -5
@@ -43,7 +43,6 @@ type EventData = {
43 type DragEventType = 'dragstart' | 'dragend' | 'dragchange' | 'dragmove';
44
45 type DragEvent = {|
46 - listener: DragEvent => void,
46 target: Element | Document,
47 type: DragEventType,
48 diffX?: number,
@@ -53,11 +52,9 @@ type DragEvent = {|
52 function createDragEvent(
53 type: DragEventType,
54 target: Element | Document,
56 - listener: DragEvent => void,
55 eventData?: EventData,
56 ): DragEvent {
57 return {
60 - listener,
58 target,
59 type,
60 ...eventData,
@@ -73,8 +70,8 @@ function dispatchDragEvent(
70 eventData?: EventData,
71 ): void {
72 const target = ((state.dragTarget: any): Element | Document);
76 - const syntheticEvent = createDragEvent(name, target, listener, eventData);
77 - context.dispatchEvent(syntheticEvent, {discrete});
73 + const syntheticEvent = createDragEvent(name, target, eventData);
74 + context.dispatchEvent(syntheticEvent, listener, {discrete});
75 }
76
77 const DragResponder = {
packages/react-events/src/Focus.js
+8 -11
@@ -27,7 +27,6 @@ type FocusState = {
27 type FocusEventType = 'focus' | 'blur' | 'focuschange';
28
29 type FocusEvent = {|
30 - listener: FocusEvent => void,
30 target: Element | Document,
31 type: FocusEventType,
32 |};
@@ -40,10 +39,8 @@ const targetEventTypes = [
39 function createFocusEvent(
40 type: FocusEventType,
41 target: Element | Document,
43 - listener: FocusEvent => void,
42 ): FocusEvent {
43 return {
46 - listener,
44 target,
45 type,
46 };
@@ -59,15 +56,15 @@ function dispatchFocusInEvents(
56 return;
57 }
58 if (props.onFocus) {
62 - const syntheticEvent = createFocusEvent('focus', target, props.onFocus);
63 - context.dispatchEvent(syntheticEvent, {discrete: true});
59 + const syntheticEvent = createFocusEvent('focus', target);
60 + context.dispatchEvent(syntheticEvent, props.onFocus, {discrete: true});
61 }
62 if (props.onFocusChange) {
63 const listener = () => {
64 props.onFocusChange(true);
65 };
69 - const syntheticEvent = createFocusEvent('focuschange', target, listener);
70 - context.dispatchEvent(syntheticEvent, {discrete: true});
66 + const syntheticEvent = createFocusEvent('focuschange', target);
67 + context.dispatchEvent(syntheticEvent, listener, {discrete: true});
68 }
69 }
70
@@ -81,15 +78,15 @@ function dispatchFocusOutEvents(
78 return;
79 }
80 if (props.onBlur) {
84 - const syntheticEvent = createFocusEvent('blur', target, props.onBlur);
85 - context.dispatchEvent(syntheticEvent, {discrete: true});
81 + const syntheticEvent = createFocusEvent('blur', target);
82 + context.dispatchEvent(syntheticEvent, props.onBlur, {discrete: true});
83 }
84 if (props.onFocusChange) {
85 const listener = () => {
86 props.onFocusChange(false);
87 };
91 - const syntheticEvent = createFocusEvent('focuschange', target, listener);
92 - context.dispatchEvent(syntheticEvent, {discrete: true});
88 + const syntheticEvent = createFocusEvent('focuschange', target);
89 + context.dispatchEvent(syntheticEvent, listener, {discrete: true});
90 }
91 }
92
packages/react-events/src/Hover.js
+11 -23
@@ -36,7 +36,6 @@ type HoverState = {
36 type HoverEventType = 'hoverstart' | 'hoverend' | 'hoverchange' | 'hovermove';
37
38 type HoverEvent = {|
39 - listener: HoverEvent => void,
39 target: Element | Document,
40 type: HoverEventType,
41 |};
@@ -59,10 +58,8 @@ if (typeof window !== 'undefined' && window.PointerEvent === undefined) {
58 function createHoverEvent(
59 type: HoverEventType,
60 target: Element | Document,
62 - listener: HoverEvent => void,
61 ): HoverEvent {
62 return {
65 - listener,
63 target,
64 type,
65 };
@@ -78,12 +75,8 @@ function dispatchHoverChangeEvent(
75 const listener = () => {
76 props.onHoverChange(bool);
77 };
81 - const syntheticEvent = createHoverEvent(
82 - 'hoverchange',
83 - event.target,
84 - listener,
85 - );
86 - context.dispatchEvent(syntheticEvent, {discrete: true});
78 + const syntheticEvent = createHoverEvent('hoverchange', event.target);
79 + context.dispatchEvent(syntheticEvent, listener, {discrete: true});
80 }
81
82 function dispatchHoverStartEvents(
@@ -108,12 +101,10 @@ function dispatchHoverStartEvents(
101 state.isActiveHovered = true;
102
103 if (props.onHoverStart) {
111 - const syntheticEvent = createHoverEvent(
112 - 'hoverstart',
113 - target,
114 - props.onHoverStart,
115 - );
116 - context.dispatchEvent(syntheticEvent, {discrete: true});
104 + const syntheticEvent = createHoverEvent('hoverstart', target);
105 + context.dispatchEvent(syntheticEvent, props.onHoverStart, {
106 + discrete: true,
107 + });
108 }
109 if (props.onHoverChange) {
110 dispatchHoverChangeEvent(event, context, props, state);
@@ -159,12 +150,8 @@ function dispatchHoverEndEvents(
150 state.isActiveHovered = false;
151
152 if (props.onHoverEnd) {
162 - const syntheticEvent = createHoverEvent(
163 - 'hoverend',
164 - target,
165 - props.onHoverEnd,
166 - );
167 - context.dispatchEvent(syntheticEvent, {discrete: true});
153 + const syntheticEvent = createHoverEvent('hoverend', target);
154 + context.dispatchEvent(syntheticEvent, props.onHoverEnd, {discrete: true});
155 }
156 if (props.onHoverChange) {
157 dispatchHoverChangeEvent(event, context, props, state);
@@ -292,9 +279,10 @@ const HoverResponder = {
279 const syntheticEvent = createHoverEvent(
280 'hovermove',
281 event.target,
295 - props.onHoverMove,
282 );
297 - context.dispatchEvent(syntheticEvent, {discrete: false});
283 + context.dispatchEvent(syntheticEvent, props.onHoverMove, {
284 + discrete: false,
285 + });
286 }
287 }
288 }
packages/react-events/src/Press.js
+2 -4
@@ -71,7 +71,6 @@ type PressEventType =
71 | 'longpresschange';
72
73 type PressEvent = {|
74 - listener: PressEvent => void,
74 target: Element | Document,
75 type: PressEventType,
76 pointerType: PointerType,
@@ -115,11 +114,9 @@ if (typeof window !== 'undefined' && window.PointerEvent === undefined) {
114 function createPressEvent(
115 type: PressEventType,
116 target: Element | Document,
118 - listener: PressEvent => void,
117 pointerType: PointerType,
118 ): PressEvent {
119 return {
122 - listener,
120 target,
121 type,
122 pointerType,
@@ -135,9 +132,10 @@ function dispatchEvent(
132 ): void {
133 const target = ((state.pressTarget: any): Element | Document);
134 const pointerType = state.pointerType;
138 - const syntheticEvent = createPressEvent(name, target, listener, pointerType);
135 + const syntheticEvent = createPressEvent(name, target, pointerType);
136 context.dispatchEvent(
137 syntheticEvent,
138 + listener,
139 options || {
140 discrete: true,
141 },
packages/react-events/src/Swipe.js
+2 -5
@@ -33,7 +33,6 @@ type EventData = {
33 type SwipeEventType = 'swipeleft' | 'swiperight' | 'swipeend' | 'swipemove';
34
35 type SwipeEvent = {|
36 - listener: SwipeEvent => void,
36 target: Element | Document,
37 type: SwipeEventType,
38 diffX?: number,
@@ -43,11 +42,9 @@ type SwipeEvent = {|
42 function createSwipeEvent(
43 type: SwipeEventType,
44 target: Element | Document,
46 - listener: SwipeEvent => void,
45 eventData?: EventData,
46 ): SwipeEvent {
47 return {
50 - listener,
48 target,
49 type,
50 ...eventData,
@@ -63,8 +60,8 @@ function dispatchSwipeEvent(
60 eventData?: EventData,
61 ) {
62 const target = ((state.swipeTarget: any): Element | Document);
66 - const syntheticEvent = createSwipeEvent(name, target, listener, eventData);
67 - context.dispatchEvent(syntheticEvent, {discrete});
63 + const syntheticEvent = createSwipeEvent(name, target, eventData);
64 + context.dispatchEvent(syntheticEvent, listener, {discrete});
65 }
66
67 type SwipeState = {
packages/shared/ReactTypes.js
+1
@@ -145,6 +145,7 @@ export type ReactResponderDispatchEventOptions = {
145 export type ReactResponderContext = {
146 dispatchEvent: (
147 eventObject: Object,
148 + listener: (Object) => void,
149 otpions: ReactResponderDispatchEventOptions,
150 ) => void,
151 dispatchStopPropagation: (passive?: boolean) => void,