Skip to content

Commit

Permalink
Create Synthetic Events Lazily (#19909)
Browse files Browse the repository at this point in the history
  • Loading branch information
gaearon authored Sep 25, 2020
1 parent 0a00804 commit 480626a
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 114 deletions.
87 changes: 33 additions & 54 deletions packages/react-dom/src/events/DOMPluginEventSystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -709,31 +709,19 @@ function createDispatchListener(
};
}

function createDispatchEntry(
event: ReactSyntheticEvent,
listeners: Array<DispatchListener>,
): DispatchEntry {
return {
event,
listeners,
};
}

export function accumulateSinglePhaseListeners(
targetFiber: Fiber | null,
dispatchQueue: DispatchQueue,
event: ReactSyntheticEvent,
reactName: string | null,
nativeEventType: string,
inCapturePhase: boolean,
accumulateTargetOnly: boolean,
): void {
const bubbleName = event._reactName;
const captureName = bubbleName !== null ? bubbleName + 'Capture' : null;
const reactEventName = inCapturePhase ? captureName : bubbleName;
): Array<DispatchListener> {
const captureName = reactName !== null ? reactName + 'Capture' : null;
const reactEventName = inCapturePhase ? captureName : reactName;
const listeners: Array<DispatchListener> = [];

let instance = targetFiber;
let lastHostComponent = null;
const targetType = event.nativeEvent.type;

// Accumulate all instances and listeners via the target -> root path.
while (instance !== null) {
Expand All @@ -749,7 +737,10 @@ export function accumulateSinglePhaseListeners(
);
if (eventHandlerListeners !== null) {
eventHandlerListeners.forEach(entry => {
if (entry.type === targetType && entry.capture === inCapturePhase) {
if (
entry.type === nativeEventType &&
entry.capture === inCapturePhase
) {
listeners.push(
createDispatchListener(
instance,
Expand Down Expand Up @@ -785,7 +776,10 @@ export function accumulateSinglePhaseListeners(
);
if (eventHandlerListeners !== null) {
eventHandlerListeners.forEach(entry => {
if (entry.type === targetType && entry.capture === inCapturePhase) {
if (
entry.type === nativeEventType &&
entry.capture === inCapturePhase
) {
listeners.push(
createDispatchListener(
instance,
Expand All @@ -805,9 +799,7 @@ export function accumulateSinglePhaseListeners(
}
instance = instance.return;
}
if (listeners.length !== 0) {
dispatchQueue.push(createDispatchEntry(event, listeners));
}
return listeners;
}

// We should only use this function for:
Expand All @@ -819,11 +811,9 @@ export function accumulateSinglePhaseListeners(
// phase event listeners (via emulation).
export function accumulateTwoPhaseListeners(
targetFiber: Fiber | null,
dispatchQueue: DispatchQueue,
event: ReactSyntheticEvent,
): void {
const bubbleName = event._reactName;
const captureName = bubbleName !== null ? bubbleName + 'Capture' : null;
reactName: string,
): Array<DispatchListener> {
const captureName = reactName + 'Capture';
const listeners: Array<DispatchListener> = [];
let instance = targetFiber;

Expand All @@ -833,29 +823,22 @@ export function accumulateTwoPhaseListeners(
// Handle listeners that are on HostComponents (i.e. <div>)
if (tag === HostComponent && stateNode !== null) {
const currentTarget = stateNode;
// Standard React on* listeners, i.e. onClick prop
if (captureName !== null) {
const captureListener = getListener(instance, captureName);
if (captureListener != null) {
listeners.unshift(
createDispatchListener(instance, captureListener, currentTarget),
);
}
const captureListener = getListener(instance, captureName);
if (captureListener != null) {
listeners.unshift(
createDispatchListener(instance, captureListener, currentTarget),
);
}
if (bubbleName !== null) {
const bubbleListener = getListener(instance, bubbleName);
if (bubbleListener != null) {
listeners.push(
createDispatchListener(instance, bubbleListener, currentTarget),
);
}
const bubbleListener = getListener(instance, reactName);
if (bubbleListener != null) {
listeners.push(
createDispatchListener(instance, bubbleListener, currentTarget),
);
}
}
instance = instance.return;
}
if (listeners.length !== 0) {
dispatchQueue.push(createDispatchEntry(event, listeners));
}
return listeners;
}

function getParent(inst: Fiber | null): Fiber | null {
Expand Down Expand Up @@ -956,7 +939,7 @@ function accumulateEnterLeaveListenersForEvent(
instance = instance.return;
}
if (listeners.length !== 0) {
dispatchQueue.push(createDispatchEntry(event, listeners));
dispatchQueue.push({event, listeners});
}
}

Expand Down Expand Up @@ -995,27 +978,23 @@ export function accumulateEnterLeaveTwoPhaseListeners(
}

export function accumulateEventHandleNonManagedNodeListeners(
dispatchQueue: DispatchQueue,
event: ReactSyntheticEvent,
reactEventType: DOMEventName,
currentTarget: EventTarget,
inCapturePhase: boolean,
): void {
): Array<DispatchListener> {
const listeners: Array<DispatchListener> = [];

const eventListeners = getEventHandlerListeners(currentTarget);
if (eventListeners !== null) {
const targetType = ((event.type: any): DOMEventName);
eventListeners.forEach(entry => {
if (entry.type === targetType && entry.capture === inCapturePhase) {
if (entry.type === reactEventType && entry.capture === inCapturePhase) {
listeners.push(
createDispatchListener(null, entry.callback, currentTarget),
);
}
});
}
if (listeners.length !== 0) {
dispatchQueue.push(createDispatchEntry(event, listeners));
}
return listeners;
}

export function getListenerSetKey(
Expand Down
57 changes: 31 additions & 26 deletions packages/react-dom/src/events/plugins/BeforeInputEventPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -226,23 +226,25 @@ function extractCompositionEvent(
}
}

const event = new SyntheticCompositionEvent(
eventType,
domEventName,
null,
nativeEvent,
nativeEventTarget,
);
accumulateTwoPhaseListeners(targetInst, dispatchQueue, event);

if (fallbackData) {
// Inject data generated from fallback path into the synthetic event.
// This matches the property of native CompositionEventInterface.
event.data = fallbackData;
} else {
const customData = getDataFromCustomEvent(nativeEvent);
if (customData !== null) {
event.data = customData;
const listeners = accumulateTwoPhaseListeners(targetInst, eventType);
if (listeners.length > 0) {
const event = new SyntheticCompositionEvent(
eventType,
domEventName,
null,
nativeEvent,
nativeEventTarget,
);
dispatchQueue.push({event, listeners});
if (fallbackData) {
// Inject data generated from fallback path into the synthetic event.
// This matches the property of native CompositionEventInterface.
event.data = fallbackData;
} else {
const customData = getDataFromCustomEvent(nativeEvent);
if (customData !== null) {
event.data = customData;
}
}
}
}
Expand Down Expand Up @@ -394,15 +396,18 @@ function extractBeforeInputEvent(
return null;
}

const event = new SyntheticInputEvent(
'onBeforeInput',
'beforeinput',
null,
nativeEvent,
nativeEventTarget,
);
accumulateTwoPhaseListeners(targetInst, dispatchQueue, event);
event.data = chars;
const listeners = accumulateTwoPhaseListeners(targetInst, 'onBeforeInput');
if (listeners.length > 0) {
const event = new SyntheticInputEvent(
'onBeforeInput',
'beforeinput',
null,
nativeEvent,
nativeEventTarget,
);
dispatchQueue.push({event, listeners});
event.data = chars;
}
}

/**
Expand Down
19 changes: 11 additions & 8 deletions packages/react-dom/src/events/plugins/ChangeEventPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,19 @@ function createAndAccumulateChangeEvent(
nativeEvent,
target,
) {
const event = new SyntheticEvent(
'onChange',
'change',
null,
nativeEvent,
target,
);
// Flag this event loop as needing state restore.
enqueueStateRestore(((target: any): Node));
accumulateTwoPhaseListeners(inst, dispatchQueue, event);
const listeners = accumulateTwoPhaseListeners(inst, 'onChange');
if (listeners.length > 0) {
const event = new SyntheticEvent(
'onChange',
'change',
null,
nativeEvent,
target,
);
dispatchQueue.push({event, listeners});
}
}
/**
* For IE shims
Expand Down
25 changes: 13 additions & 12 deletions packages/react-dom/src/events/plugins/SelectEventPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,20 +113,21 @@ function constructSelectEvent(dispatchQueue, nativeEvent, nativeEventTarget) {
if (!lastSelection || !shallowEqual(lastSelection, currentSelection)) {
lastSelection = currentSelection;

const syntheticEvent = new SyntheticEvent(
'onSelect',
'select',
null,
nativeEvent,
nativeEventTarget,
);
syntheticEvent.target = activeElement;

accumulateTwoPhaseListeners(
const listeners = accumulateTwoPhaseListeners(
activeElementInst,
dispatchQueue,
syntheticEvent,
'onSelect',
);
if (listeners.length > 0) {
const event = new SyntheticEvent(
'onSelect',
'select',
null,
nativeEvent,
nativeEventTarget,
);
dispatchQueue.push({event, listeners});
event.target = activeElement;
}
}
}

Expand Down
44 changes: 30 additions & 14 deletions packages/react-dom/src/events/plugins/SimpleEventPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ function extractEvents(
return;
}
let SyntheticEventCtor = SyntheticEvent;
let reactEventType = domEventName;
let reactEventType: string = domEventName;
switch (domEventName) {
case 'keypress':
// Firefox creates a keypress event for function keys too. This removes
Expand Down Expand Up @@ -157,25 +157,30 @@ function extractEvents(
// Unknown event. This is used by createEventHandle.
break;
}
const event = new SyntheticEventCtor(
reactName,
reactEventType,
null,
nativeEvent,
nativeEventTarget,
);

const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
if (
enableCreateEventHandleAPI &&
eventSystemFlags & IS_EVENT_HANDLE_NON_MANAGED_NODE
) {
accumulateEventHandleNonManagedNodeListeners(
dispatchQueue,
event,
const listeners = accumulateEventHandleNonManagedNodeListeners(
// TODO: this cast may not make sense for events like
// "focus" where React listens to e.g. "focusin".
((reactEventType: any): DOMEventName),
targetContainer,
inCapturePhase,
);
if (listeners.length > 0) {
// Intentionally create event lazily.
const event = new SyntheticEventCtor(
reactName,
reactEventType,
null,
nativeEvent,
nativeEventTarget,
);
dispatchQueue.push({event, listeners});
}
} else {
// Some events don't bubble in the browser.
// In the past, React has always bubbled them, but this can be surprising.
Expand All @@ -189,13 +194,24 @@ function extractEvents(
// This is a breaking change that can wait until React 18.
domEventName === 'scroll';

accumulateSinglePhaseListeners(
const listeners = accumulateSinglePhaseListeners(
targetInst,
dispatchQueue,
event,
reactName,
nativeEvent.type,
inCapturePhase,
accumulateTargetOnly,
);
if (listeners.length > 0) {
// Intentionally create event lazily.
const event = new SyntheticEventCtor(
reactName,
reactEventType,
null,
nativeEvent,
nativeEventTarget,
);
dispatchQueue.push({event, listeners});
}
}
}

Expand Down

0 comments on commit 480626a

Please sign in to comment.