Skip to content

Commit 480626a

Browse files
authored
Create Synthetic Events Lazily (#19909)
1 parent 0a00804 commit 480626a

File tree

5 files changed

+118
-114
lines changed

5 files changed

+118
-114
lines changed

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

Lines changed: 33 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -709,31 +709,19 @@ function createDispatchListener(
709709
};
710710
}
711711

712-
function createDispatchEntry(
713-
event: ReactSyntheticEvent,
714-
listeners: Array<DispatchListener>,
715-
): DispatchEntry {
716-
return {
717-
event,
718-
listeners,
719-
};
720-
}
721-
722712
export function accumulateSinglePhaseListeners(
723713
targetFiber: Fiber | null,
724-
dispatchQueue: DispatchQueue,
725-
event: ReactSyntheticEvent,
714+
reactName: string | null,
715+
nativeEventType: string,
726716
inCapturePhase: boolean,
727717
accumulateTargetOnly: boolean,
728-
): void {
729-
const bubbleName = event._reactName;
730-
const captureName = bubbleName !== null ? bubbleName + 'Capture' : null;
731-
const reactEventName = inCapturePhase ? captureName : bubbleName;
718+
): Array<DispatchListener> {
719+
const captureName = reactName !== null ? reactName + 'Capture' : null;
720+
const reactEventName = inCapturePhase ? captureName : reactName;
732721
const listeners: Array<DispatchListener> = [];
733722

734723
let instance = targetFiber;
735724
let lastHostComponent = null;
736-
const targetType = event.nativeEvent.type;
737725

738726
// Accumulate all instances and listeners via the target -> root path.
739727
while (instance !== null) {
@@ -749,7 +737,10 @@ export function accumulateSinglePhaseListeners(
749737
);
750738
if (eventHandlerListeners !== null) {
751739
eventHandlerListeners.forEach(entry => {
752-
if (entry.type === targetType && entry.capture === inCapturePhase) {
740+
if (
741+
entry.type === nativeEventType &&
742+
entry.capture === inCapturePhase
743+
) {
753744
listeners.push(
754745
createDispatchListener(
755746
instance,
@@ -785,7 +776,10 @@ export function accumulateSinglePhaseListeners(
785776
);
786777
if (eventHandlerListeners !== null) {
787778
eventHandlerListeners.forEach(entry => {
788-
if (entry.type === targetType && entry.capture === inCapturePhase) {
779+
if (
780+
entry.type === nativeEventType &&
781+
entry.capture === inCapturePhase
782+
) {
789783
listeners.push(
790784
createDispatchListener(
791785
instance,
@@ -805,9 +799,7 @@ export function accumulateSinglePhaseListeners(
805799
}
806800
instance = instance.return;
807801
}
808-
if (listeners.length !== 0) {
809-
dispatchQueue.push(createDispatchEntry(event, listeners));
810-
}
802+
return listeners;
811803
}
812804

813805
// We should only use this function for:
@@ -819,11 +811,9 @@ export function accumulateSinglePhaseListeners(
819811
// phase event listeners (via emulation).
820812
export function accumulateTwoPhaseListeners(
821813
targetFiber: Fiber | null,
822-
dispatchQueue: DispatchQueue,
823-
event: ReactSyntheticEvent,
824-
): void {
825-
const bubbleName = event._reactName;
826-
const captureName = bubbleName !== null ? bubbleName + 'Capture' : null;
814+
reactName: string,
815+
): Array<DispatchListener> {
816+
const captureName = reactName + 'Capture';
827817
const listeners: Array<DispatchListener> = [];
828818
let instance = targetFiber;
829819

@@ -833,29 +823,22 @@ export function accumulateTwoPhaseListeners(
833823
// Handle listeners that are on HostComponents (i.e. <div>)
834824
if (tag === HostComponent && stateNode !== null) {
835825
const currentTarget = stateNode;
836-
// Standard React on* listeners, i.e. onClick prop
837-
if (captureName !== null) {
838-
const captureListener = getListener(instance, captureName);
839-
if (captureListener != null) {
840-
listeners.unshift(
841-
createDispatchListener(instance, captureListener, currentTarget),
842-
);
843-
}
826+
const captureListener = getListener(instance, captureName);
827+
if (captureListener != null) {
828+
listeners.unshift(
829+
createDispatchListener(instance, captureListener, currentTarget),
830+
);
844831
}
845-
if (bubbleName !== null) {
846-
const bubbleListener = getListener(instance, bubbleName);
847-
if (bubbleListener != null) {
848-
listeners.push(
849-
createDispatchListener(instance, bubbleListener, currentTarget),
850-
);
851-
}
832+
const bubbleListener = getListener(instance, reactName);
833+
if (bubbleListener != null) {
834+
listeners.push(
835+
createDispatchListener(instance, bubbleListener, currentTarget),
836+
);
852837
}
853838
}
854839
instance = instance.return;
855840
}
856-
if (listeners.length !== 0) {
857-
dispatchQueue.push(createDispatchEntry(event, listeners));
858-
}
841+
return listeners;
859842
}
860843

861844
function getParent(inst: Fiber | null): Fiber | null {
@@ -956,7 +939,7 @@ function accumulateEnterLeaveListenersForEvent(
956939
instance = instance.return;
957940
}
958941
if (listeners.length !== 0) {
959-
dispatchQueue.push(createDispatchEntry(event, listeners));
942+
dispatchQueue.push({event, listeners});
960943
}
961944
}
962945

@@ -995,27 +978,23 @@ export function accumulateEnterLeaveTwoPhaseListeners(
995978
}
996979

997980
export function accumulateEventHandleNonManagedNodeListeners(
998-
dispatchQueue: DispatchQueue,
999-
event: ReactSyntheticEvent,
981+
reactEventType: DOMEventName,
1000982
currentTarget: EventTarget,
1001983
inCapturePhase: boolean,
1002-
): void {
984+
): Array<DispatchListener> {
1003985
const listeners: Array<DispatchListener> = [];
1004986

1005987
const eventListeners = getEventHandlerListeners(currentTarget);
1006988
if (eventListeners !== null) {
1007-
const targetType = ((event.type: any): DOMEventName);
1008989
eventListeners.forEach(entry => {
1009-
if (entry.type === targetType && entry.capture === inCapturePhase) {
990+
if (entry.type === reactEventType && entry.capture === inCapturePhase) {
1010991
listeners.push(
1011992
createDispatchListener(null, entry.callback, currentTarget),
1012993
);
1013994
}
1014995
});
1015996
}
1016-
if (listeners.length !== 0) {
1017-
dispatchQueue.push(createDispatchEntry(event, listeners));
1018-
}
997+
return listeners;
1019998
}
1020999

10211000
export function getListenerSetKey(

packages/react-dom/src/events/plugins/BeforeInputEventPlugin.js

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -226,23 +226,25 @@ function extractCompositionEvent(
226226
}
227227
}
228228

229-
const event = new SyntheticCompositionEvent(
230-
eventType,
231-
domEventName,
232-
null,
233-
nativeEvent,
234-
nativeEventTarget,
235-
);
236-
accumulateTwoPhaseListeners(targetInst, dispatchQueue, event);
237-
238-
if (fallbackData) {
239-
// Inject data generated from fallback path into the synthetic event.
240-
// This matches the property of native CompositionEventInterface.
241-
event.data = fallbackData;
242-
} else {
243-
const customData = getDataFromCustomEvent(nativeEvent);
244-
if (customData !== null) {
245-
event.data = customData;
229+
const listeners = accumulateTwoPhaseListeners(targetInst, eventType);
230+
if (listeners.length > 0) {
231+
const event = new SyntheticCompositionEvent(
232+
eventType,
233+
domEventName,
234+
null,
235+
nativeEvent,
236+
nativeEventTarget,
237+
);
238+
dispatchQueue.push({event, listeners});
239+
if (fallbackData) {
240+
// Inject data generated from fallback path into the synthetic event.
241+
// This matches the property of native CompositionEventInterface.
242+
event.data = fallbackData;
243+
} else {
244+
const customData = getDataFromCustomEvent(nativeEvent);
245+
if (customData !== null) {
246+
event.data = customData;
247+
}
246248
}
247249
}
248250
}
@@ -394,15 +396,18 @@ function extractBeforeInputEvent(
394396
return null;
395397
}
396398

397-
const event = new SyntheticInputEvent(
398-
'onBeforeInput',
399-
'beforeinput',
400-
null,
401-
nativeEvent,
402-
nativeEventTarget,
403-
);
404-
accumulateTwoPhaseListeners(targetInst, dispatchQueue, event);
405-
event.data = chars;
399+
const listeners = accumulateTwoPhaseListeners(targetInst, 'onBeforeInput');
400+
if (listeners.length > 0) {
401+
const event = new SyntheticInputEvent(
402+
'onBeforeInput',
403+
'beforeinput',
404+
null,
405+
nativeEvent,
406+
nativeEventTarget,
407+
);
408+
dispatchQueue.push({event, listeners});
409+
event.data = chars;
410+
}
406411
}
407412

408413
/**

packages/react-dom/src/events/plugins/ChangeEventPlugin.js

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,19 @@ function createAndAccumulateChangeEvent(
4949
nativeEvent,
5050
target,
5151
) {
52-
const event = new SyntheticEvent(
53-
'onChange',
54-
'change',
55-
null,
56-
nativeEvent,
57-
target,
58-
);
5952
// Flag this event loop as needing state restore.
6053
enqueueStateRestore(((target: any): Node));
61-
accumulateTwoPhaseListeners(inst, dispatchQueue, event);
54+
const listeners = accumulateTwoPhaseListeners(inst, 'onChange');
55+
if (listeners.length > 0) {
56+
const event = new SyntheticEvent(
57+
'onChange',
58+
'change',
59+
null,
60+
nativeEvent,
61+
target,
62+
);
63+
dispatchQueue.push({event, listeners});
64+
}
6265
}
6366
/**
6467
* For IE shims

packages/react-dom/src/events/plugins/SelectEventPlugin.js

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -113,20 +113,21 @@ function constructSelectEvent(dispatchQueue, nativeEvent, nativeEventTarget) {
113113
if (!lastSelection || !shallowEqual(lastSelection, currentSelection)) {
114114
lastSelection = currentSelection;
115115

116-
const syntheticEvent = new SyntheticEvent(
117-
'onSelect',
118-
'select',
119-
null,
120-
nativeEvent,
121-
nativeEventTarget,
122-
);
123-
syntheticEvent.target = activeElement;
124-
125-
accumulateTwoPhaseListeners(
116+
const listeners = accumulateTwoPhaseListeners(
126117
activeElementInst,
127-
dispatchQueue,
128-
syntheticEvent,
118+
'onSelect',
129119
);
120+
if (listeners.length > 0) {
121+
const event = new SyntheticEvent(
122+
'onSelect',
123+
'select',
124+
null,
125+
nativeEvent,
126+
nativeEventTarget,
127+
);
128+
dispatchQueue.push({event, listeners});
129+
event.target = activeElement;
130+
}
130131
}
131132
}
132133

packages/react-dom/src/events/plugins/SimpleEventPlugin.js

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ function extractEvents(
6363
return;
6464
}
6565
let SyntheticEventCtor = SyntheticEvent;
66-
let reactEventType = domEventName;
66+
let reactEventType: string = domEventName;
6767
switch (domEventName) {
6868
case 'keypress':
6969
// Firefox creates a keypress event for function keys too. This removes
@@ -157,25 +157,30 @@ function extractEvents(
157157
// Unknown event. This is used by createEventHandle.
158158
break;
159159
}
160-
const event = new SyntheticEventCtor(
161-
reactName,
162-
reactEventType,
163-
null,
164-
nativeEvent,
165-
nativeEventTarget,
166-
);
167160

168161
const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
169162
if (
170163
enableCreateEventHandleAPI &&
171164
eventSystemFlags & IS_EVENT_HANDLE_NON_MANAGED_NODE
172165
) {
173-
accumulateEventHandleNonManagedNodeListeners(
174-
dispatchQueue,
175-
event,
166+
const listeners = accumulateEventHandleNonManagedNodeListeners(
167+
// TODO: this cast may not make sense for events like
168+
// "focus" where React listens to e.g. "focusin".
169+
((reactEventType: any): DOMEventName),
176170
targetContainer,
177171
inCapturePhase,
178172
);
173+
if (listeners.length > 0) {
174+
// Intentionally create event lazily.
175+
const event = new SyntheticEventCtor(
176+
reactName,
177+
reactEventType,
178+
null,
179+
nativeEvent,
180+
nativeEventTarget,
181+
);
182+
dispatchQueue.push({event, listeners});
183+
}
179184
} else {
180185
// Some events don't bubble in the browser.
181186
// In the past, React has always bubbled them, but this can be surprising.
@@ -189,13 +194,24 @@ function extractEvents(
189194
// This is a breaking change that can wait until React 18.
190195
domEventName === 'scroll';
191196

192-
accumulateSinglePhaseListeners(
197+
const listeners = accumulateSinglePhaseListeners(
193198
targetInst,
194-
dispatchQueue,
195-
event,
199+
reactName,
200+
nativeEvent.type,
196201
inCapturePhase,
197202
accumulateTargetOnly,
198203
);
204+
if (listeners.length > 0) {
205+
// Intentionally create event lazily.
206+
const event = new SyntheticEventCtor(
207+
reactName,
208+
reactEventType,
209+
null,
210+
nativeEvent,
211+
nativeEventTarget,
212+
);
213+
dispatchQueue.push({event, listeners});
214+
}
199215
}
200216
}
201217

0 commit comments

Comments
 (0)