Skip to content

Commit 784ebd8

Browse files
authored
Experimental event API: rework the propagation system for event components (#15462)
1 parent 5876769 commit 784ebd8

File tree

9 files changed

+342
-434
lines changed

9 files changed

+342
-434
lines changed

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

Lines changed: 48 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
HostComponent,
1919
} from 'shared/ReactWorkTags';
2020
import type {
21+
ReactEventResponder,
2122
ReactEventResponderEventType,
2223
ReactEventComponentInstance,
2324
ReactResponderContext,
@@ -61,13 +62,10 @@ type ResponderTimeout = {|
6162

6263
type ResponderTimer = {|
6364
instance: ReactEventComponentInstance,
64-
func: () => boolean,
65+
func: () => void,
6566
id: Symbol,
6667
|};
6768

68-
const ROOT_PHASE = 0;
69-
const BUBBLE_PHASE = 1;
70-
const CAPTURE_PHASE = 2;
7169
const activeTimeouts: Map<Symbol, ResponderTimeout> = new Map();
7270
const rootEventTypesToEventComponentInstances: Map<
7371
DOMTopLevelEventType | string,
@@ -253,7 +251,7 @@ const eventResponderContext: ReactResponderContext = {
253251
triggerOwnershipListeners();
254252
return false;
255253
},
256-
setTimeout(func: () => boolean, delay): Symbol {
254+
setTimeout(func: () => void, delay): Symbol {
257255
validateResponderContext();
258256
if (currentTimers === null) {
259257
currentTimers = new Map();
@@ -349,16 +347,13 @@ const eventResponderContext: ReactResponderContext = {
349347

350348
function processTimers(timers: Map<Symbol, ResponderTimer>): void {
351349
const timersArr = Array.from(timers.values());
352-
let shouldStopPropagation = false;
353350
currentEventQueue = createEventQueue();
354351
try {
355352
for (let i = 0; i < timersArr.length; i++) {
356353
const {instance, func, id} = timersArr[i];
357354
currentInstance = instance;
358355
try {
359-
if (!shouldStopPropagation) {
360-
shouldStopPropagation = func();
361-
}
356+
func();
362357
} finally {
363358
activeTimeouts.delete(id);
364359
}
@@ -390,15 +385,13 @@ function createResponderEvent(
390385
nativeEvent: AnyNativeEvent,
391386
nativeEventTarget: Element | Document,
392387
eventSystemFlags: EventSystemFlags,
393-
phase: 0 | 1 | 2,
394388
): ReactResponderEvent {
395389
const responderEvent = {
396390
nativeEvent: nativeEvent,
397391
target: nativeEventTarget,
398392
type: topLevelType,
399393
passive: (eventSystemFlags & IS_PASSIVE) !== 0,
400394
passiveSupported: (eventSystemFlags & PASSIVE_NOT_SUPPORTED) === 0,
401-
phase,
402395
};
403396
if (__DEV__) {
404397
Object.freeze(responderEvent);
@@ -510,16 +503,7 @@ function getRootEventResponderInstances(
510503
return eventResponderInstances;
511504
}
512505

513-
function triggerEventResponderEventListener(
514-
responderEvent: ReactResponderEvent,
515-
eventComponentInstance: ReactEventComponentInstance,
516-
): boolean {
517-
const {responder, props, state} = eventComponentInstance;
518-
currentInstance = eventComponentInstance;
519-
return responder.onEvent(responderEvent, eventResponderContext, props, state);
520-
}
521-
522-
function traverseAndTriggerEventResponderInstances(
506+
function traverseAndHandleEventResponderInstances(
523507
topLevelType: DOMTopLevelEventType,
524508
targetFiber: null | Fiber,
525509
nativeEvent: AnyNativeEvent,
@@ -535,46 +519,54 @@ function traverseAndTriggerEventResponderInstances(
535519
topLevelType,
536520
targetFiber,
537521
);
522+
const responderEvent = createResponderEvent(
523+
((topLevelType: any): string),
524+
nativeEvent,
525+
((nativeEventTarget: any): Element | Document),
526+
eventSystemFlags,
527+
);
528+
const propagatedEventResponders: Set<ReactEventResponder> = new Set();
538529
let length = targetEventResponderInstances.length;
539530
let i;
540-
let shouldStopPropagation = false;
541-
let responderEvent;
542531

532+
// Captured and bubbled event phases have the notion of local propagation.
533+
// This means that the propgation chain can be stopped part of the the way
534+
// through processing event component instances. The major difference to other
535+
// events systems is that the stopping of propgation is localized to a single
536+
// phase, rather than both phases.
543537
if (length > 0) {
544538
// Capture target phase
545-
responderEvent = createResponderEvent(
546-
((topLevelType: any): string),
547-
nativeEvent,
548-
((nativeEventTarget: any): Element | Document),
549-
eventSystemFlags,
550-
CAPTURE_PHASE,
551-
);
552539
for (i = length; i-- > 0; ) {
553540
const targetEventResponderInstance = targetEventResponderInstances[i];
554-
shouldStopPropagation = triggerEventResponderEventListener(
555-
responderEvent,
556-
targetEventResponderInstance,
557-
);
558-
if (shouldStopPropagation) {
559-
return;
541+
const {responder, props, state} = targetEventResponderInstance;
542+
if (responder.stopLocalPropagation) {
543+
if (propagatedEventResponders.has(responder)) {
544+
continue;
545+
}
546+
propagatedEventResponders.add(responder);
547+
}
548+
const eventListener = responder.onEventCapture;
549+
if (eventListener !== undefined) {
550+
currentInstance = targetEventResponderInstance;
551+
eventListener(responderEvent, eventResponderContext, props, state);
560552
}
561553
}
554+
// We clean propagated event responders between phases.
555+
propagatedEventResponders.clear();
562556
// Bubble target phase
563-
responderEvent = createResponderEvent(
564-
((topLevelType: any): string),
565-
nativeEvent,
566-
((nativeEventTarget: any): Element | Document),
567-
eventSystemFlags,
568-
BUBBLE_PHASE,
569-
);
570557
for (i = 0; i < length; i++) {
571558
const targetEventResponderInstance = targetEventResponderInstances[i];
572-
shouldStopPropagation = triggerEventResponderEventListener(
573-
responderEvent,
574-
targetEventResponderInstance,
575-
);
576-
if (shouldStopPropagation) {
577-
return;
559+
const {responder, props, state} = targetEventResponderInstance;
560+
if (responder.stopLocalPropagation) {
561+
if (propagatedEventResponders.has(responder)) {
562+
continue;
563+
}
564+
propagatedEventResponders.add(responder);
565+
}
566+
const eventListener = responder.onEvent;
567+
if (eventListener !== undefined) {
568+
currentInstance = targetEventResponderInstance;
569+
eventListener(responderEvent, eventResponderContext, props, state);
578570
}
579571
}
580572
}
@@ -584,21 +576,13 @@ function traverseAndTriggerEventResponderInstances(
584576
);
585577
length = rootEventResponderInstances.length;
586578
if (length > 0) {
587-
responderEvent = createResponderEvent(
588-
((topLevelType: any): string),
589-
nativeEvent,
590-
((nativeEventTarget: any): Element | Document),
591-
eventSystemFlags,
592-
ROOT_PHASE,
593-
);
594579
for (i = 0; i < length; i++) {
595-
const targetEventResponderInstance = rootEventResponderInstances[i];
596-
shouldStopPropagation = triggerEventResponderEventListener(
597-
responderEvent,
598-
targetEventResponderInstance,
599-
);
600-
if (shouldStopPropagation) {
601-
return;
580+
const rootEventResponderInstance = rootEventResponderInstances[i];
581+
const {responder, props, state} = rootEventResponderInstance;
582+
const eventListener = responder.onRootEvent;
583+
if (eventListener !== undefined) {
584+
currentInstance = rootEventResponderInstance;
585+
eventListener(responderEvent, eventResponderContext, props, state);
602586
}
603587
}
604588
}
@@ -672,7 +656,7 @@ export function dispatchEventForResponderEventSystem(
672656
if (enableEventAPI) {
673657
currentEventQueue = createEventQueue();
674658
try {
675-
traverseAndTriggerEventResponderInstances(
659+
traverseAndHandleEventResponderInstances(
676660
topLevelType,
677661
targetFiber,
678662
nativeEvent,

0 commit comments

Comments
 (0)