@samitouri / QOS-React-2 / commits / 480626a9e9

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