Create Synthetic Events Lazily (#19909)
Dan Abramov committed
Sep 25, 2020 at 13:33 UTC
480626a9e920d5e04194c793a828318102ea4ff4
5 files changed
+118
-114
packages/react-dom/src/events/DOMPluginEventSystem.js
+33
-54
@@ -709,31 +709,19 @@ function createDispatchListener(
709
};
710
}
711
712
-function createDispatchEntry(
713
- event: ReactSyntheticEvent,
714
- listeners: Array<DispatchListener>,
715
-): DispatchEntry {
716
- return {
717
- event,
718
- listeners,
719
- };
720
-}
721
-
712
export function accumulateSinglePhaseListeners(
713
targetFiber: Fiber | null,
724
- dispatchQueue: DispatchQueue,
725
- event: ReactSyntheticEvent,
714
+ reactName: string | null,
715
+ nativeEventType: string,
716
inCapturePhase: boolean,
717
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;
721
const listeners: Array<DispatchListener> = [];
722
723
let instance = targetFiber;
724
let lastHostComponent = null;
736
- const targetType = event.nativeEvent.type;
725
726
// Accumulate all instances and listeners via the target -> root path.
727
while (instance !== null) {
@@ -749,7 +737,10 @@ export function accumulateSinglePhaseListeners(
737
);
738
if (eventHandlerListeners !== null) {
739
eventHandlerListeners.forEach(entry => {
752
- if (entry.type === targetType && entry.capture === inCapturePhase) {
740
+ if (
741
+ entry.type === nativeEventType &&
742
+ entry.capture === inCapturePhase
743
+ ) {
744
listeners.push(
745
createDispatchListener(
746
instance,
@@ -785,7 +776,10 @@ export function accumulateSinglePhaseListeners(
776
);
777
if (eventHandlerListeners !== null) {
778
eventHandlerListeners.forEach(entry => {
788
- if (entry.type === targetType && entry.capture === inCapturePhase) {
779
+ if (
780
+ entry.type === nativeEventType &&
781
+ entry.capture === inCapturePhase
782
+ ) {
783
listeners.push(
784
createDispatchListener(
785
instance,
@@ -805,9 +799,7 @@ export function accumulateSinglePhaseListeners(
799
}
800
instance = instance.return;
801
}
808
- if (listeners.length !== 0) {
809
- dispatchQueue.push(createDispatchEntry(event, listeners));
810
- }
802
+ return listeners;
803
}
804
805
// We should only use this function for:
@@ -819,11 +811,9 @@ export function accumulateSinglePhaseListeners(
811
// phase event listeners (via emulation).
812
export function accumulateTwoPhaseListeners(
813
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';
817
const listeners: Array<DispatchListener> = [];
818
let instance = targetFiber;
819
@@ -833,29 +823,22 @@ export function accumulateTwoPhaseListeners(
823
// Handle listeners that are on HostComponents (i.e. <div>)
824
if (tag === HostComponent && stateNode !== null) {
825
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
+ );
831
}
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
+ );
837
}
838
}
839
instance = instance.return;
840
}
856
- if (listeners.length !== 0) {
857
- dispatchQueue.push(createDispatchEntry(event, listeners));
858
- }
841
+ return listeners;
842
}
843
844
function getParent(inst: Fiber | null): Fiber | null {
@@ -956,7 +939,7 @@ function accumulateEnterLeaveListenersForEvent(
939
instance = instance.return;
940
}
941
if (listeners.length !== 0) {
959
- dispatchQueue.push(createDispatchEntry(event, listeners));
942
+ dispatchQueue.push({event, listeners});
943
}
944
}
945
@@ -995,27 +978,23 @@ export function accumulateEnterLeaveTwoPhaseListeners(
978
}
979
980
export function accumulateEventHandleNonManagedNodeListeners(
998
- dispatchQueue: DispatchQueue,
999
- event: ReactSyntheticEvent,
981
+ reactEventType: DOMEventName,
982
currentTarget: EventTarget,
983
inCapturePhase: boolean,
1002
-): void {
984
+): Array<DispatchListener> {
985
const listeners: Array<DispatchListener> = [];
986
987
const eventListeners = getEventHandlerListeners(currentTarget);
988
if (eventListeners !== null) {
1007
- const targetType = ((event.type: any): DOMEventName);
989
eventListeners.forEach(entry => {
1009
- if (entry.type === targetType && entry.capture === inCapturePhase) {
990
+ if (entry.type === reactEventType && entry.capture === inCapturePhase) {
991
listeners.push(
992
createDispatchListener(null, entry.callback, currentTarget),
993
);
994
}
995
});
996
}
1016
- if (listeners.length !== 0) {
1017
- dispatchQueue.push(createDispatchEntry(event, listeners));
1018
- }
997
+ return listeners;
998
}
999
1000
export function getListenerSetKey(
packages/react-dom/src/events/plugins/BeforeInputEventPlugin.js
+31
-26
@@ -226,23 +226,25 @@ function extractCompositionEvent(
226
}
227
}
228
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
+ }
248
}
249
}
250
}
@@ -394,15 +396,18 @@ function extractBeforeInputEvent(
396
return null;
397
}
398
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
+ }
411
}
412
413
/**
packages/react-dom/src/events/plugins/ChangeEventPlugin.js
+11
-8
@@ -49,16 +49,19 @@ function createAndAccumulateChangeEvent(
49
nativeEvent,
50
target,
51
) {
52
- const event = new SyntheticEvent(
53
- 'onChange',
54
- 'change',
55
- null,
56
- nativeEvent,
57
- target,
58
- );
52
// Flag this event loop as needing state restore.
53
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
+ }
65
}
66
/**
67
* For IE shims
packages/react-dom/src/events/plugins/SelectEventPlugin.js
+13
-12
@@ -113,20 +113,21 @@ function constructSelectEvent(dispatchQueue, nativeEvent, nativeEventTarget) {
113
if (!lastSelection || !shallowEqual(lastSelection, currentSelection)) {
114
lastSelection = currentSelection;
115
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(
117
activeElementInst,
127
- dispatchQueue,
128
- syntheticEvent,
118
+ 'onSelect',
119
);
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
+ }
131
}
132
}
133
packages/react-dom/src/events/plugins/SimpleEventPlugin.js
+30
-14
@@ -63,7 +63,7 @@ function extractEvents(
63
return;
64
}
65
let SyntheticEventCtor = SyntheticEvent;
66
- let reactEventType = domEventName;
66
+ let reactEventType: string = domEventName;
67
switch (domEventName) {
68
case 'keypress':
69
// Firefox creates a keypress event for function keys too. This removes
@@ -157,25 +157,30 @@ function extractEvents(
157
// Unknown event. This is used by createEventHandle.
158
break;
159
}
160
- const event = new SyntheticEventCtor(
161
- reactName,
162
- reactEventType,
163
- null,
164
- nativeEvent,
165
- nativeEventTarget,
166
- );
160
161
const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
162
if (
163
enableCreateEventHandleAPI &&
164
eventSystemFlags & IS_EVENT_HANDLE_NON_MANAGED_NODE
165
) {
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),
170
targetContainer,
171
inCapturePhase,
172
);
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
+ }
184
} else {
185
// Some events don't bubble in the browser.
186
// In the past, React has always bubbled them, but this can be surprising.
@@ -189,13 +194,24 @@ function extractEvents(
194
// This is a breaking change that can wait until React 18.
195
domEventName === 'scroll';
196
192
- accumulateSinglePhaseListeners(
197
+ const listeners = accumulateSinglePhaseListeners(
198
targetInst,
194
- dispatchQueue,
195
- event,
199
+ reactName,
200
+ nativeEvent.type,
201
inCapturePhase,
202
accumulateTargetOnly,
203
);
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
+ }
215
}
216
}
217