-
Notifications
You must be signed in to change notification settings - Fork 45.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Remove Array.from() from hot path #19908
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -726,8 +726,9 @@ export function accumulateSinglePhaseListeners( | |
inCapturePhase: boolean, | ||
accumulateTargetOnly: boolean, | ||
): void { | ||
const bubbled = event._reactName; | ||
const captured = bubbled !== null ? bubbled + 'Capture' : null; | ||
const bubbleName = event._reactName; | ||
const captureName = bubbleName !== null ? bubbleName + 'Capture' : null; | ||
const reactEventName = inCapturePhase ? captureName : bubbleName; | ||
const listeners: Array<DispatchListener> = []; | ||
|
||
let instance = targetFiber; | ||
|
@@ -739,48 +740,34 @@ export function accumulateSinglePhaseListeners( | |
const {stateNode, tag} = instance; | ||
// Handle listeners that are on HostComponents (i.e. <div>) | ||
if (tag === HostComponent && stateNode !== null) { | ||
const currentTarget = stateNode; | ||
lastHostComponent = currentTarget; | ||
// For Event Handle listeners | ||
if (enableCreateEventHandleAPI) { | ||
const eventHandlerlisteners = getEventHandlerListeners(currentTarget); | ||
lastHostComponent = stateNode; | ||
|
||
if (eventHandlerlisteners !== null) { | ||
const eventHandlerlistenersArr = Array.from(eventHandlerlisteners); | ||
for (let i = 0; i < eventHandlerlistenersArr.length; i++) { | ||
const { | ||
callback, | ||
capture: isCapturePhaseListener, | ||
type, | ||
} = eventHandlerlistenersArr[i]; | ||
if (type === targetType) { | ||
if (isCapturePhaseListener && inCapturePhase) { | ||
listeners.push( | ||
createDispatchListener(instance, callback, currentTarget), | ||
); | ||
} else if (!isCapturePhaseListener && !inCapturePhase) { | ||
listeners.push( | ||
createDispatchListener(instance, callback, currentTarget), | ||
); | ||
} | ||
// createEventHandle listeners | ||
if (enableCreateEventHandleAPI) { | ||
const eventHandlerListeners = getEventHandlerListeners( | ||
lastHostComponent, | ||
); | ||
if (eventHandlerListeners !== null) { | ||
eventHandlerListeners.forEach(entry => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the change. Array.from -> Set.forEach. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I benchmarked this before and found looping over Array.from to be much faster than Set.forEach. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What was the benchmark? Do you mean there is a pronounced difference with the sizes we're operating (5-10 items)? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To be clear my concern isn't the iteration speed itself (I would expect it to be dwarfed by everything else we do, considering the Set has a small size), but that we're allocating a non-empty array for each level that has these handlers, for each event as it goes upwards. This adds up. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It probably doesn't make much difference in either case tbh. Allocating small arrays is also cheap and we don't have a good way to measure either case here. To keep things simplistic though, we can keep the forEach as you've done. |
||
if (entry.type === targetType && entry.capture === inCapturePhase) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a simplification. We don't need to do if (a && b) {
// do thing
} else if (!a && !b) {
// do same thing
} because it is equivalent to
|
||
listeners.push( | ||
createDispatchListener( | ||
instance, | ||
entry.callback, | ||
(lastHostComponent: any), | ||
), | ||
); | ||
} | ||
} | ||
} | ||
} | ||
// Standard React on* listeners, i.e. onClick prop | ||
if (captured !== null && inCapturePhase) { | ||
const captureListener = getListener(instance, captured); | ||
if (captureListener != null) { | ||
listeners.push( | ||
createDispatchListener(instance, captureListener, currentTarget), | ||
); | ||
}); | ||
} | ||
} | ||
if (bubbled !== null && !inCapturePhase) { | ||
const bubbleListener = getListener(instance, bubbled); | ||
if (bubbleListener != null) { | ||
|
||
// Standard React on* listeners, i.e. onClick or onClickCapture | ||
if (reactEventName !== null) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is only one active event name at any given time. I read it early so no need for two conditions here. |
||
const listener = getListener(instance, reactEventName); | ||
if (listener != null) { | ||
listeners.push( | ||
createDispatchListener(instance, bubbleListener, currentTarget), | ||
createDispatchListener(instance, listener, lastHostComponent), | ||
); | ||
} | ||
} | ||
|
@@ -791,32 +778,23 @@ export function accumulateSinglePhaseListeners( | |
lastHostComponent !== null && | ||
stateNode !== null | ||
) { | ||
// Scopes | ||
const reactScopeInstance = stateNode; | ||
const eventHandlerlisteners = getEventHandlerListeners( | ||
const eventHandlerListeners = getEventHandlerListeners( | ||
reactScopeInstance, | ||
); | ||
const lastCurrentTarget = ((lastHostComponent: any): Element); | ||
|
||
if (eventHandlerlisteners !== null) { | ||
const eventHandlerlistenersArr = Array.from(eventHandlerlisteners); | ||
for (let i = 0; i < eventHandlerlistenersArr.length; i++) { | ||
const { | ||
callback, | ||
capture: isCapturePhaseListener, | ||
type, | ||
} = eventHandlerlistenersArr[i]; | ||
if (type === targetType) { | ||
if (isCapturePhaseListener && inCapturePhase) { | ||
listeners.push( | ||
createDispatchListener(instance, callback, lastCurrentTarget), | ||
); | ||
} else if (!isCapturePhaseListener && !inCapturePhase) { | ||
listeners.push( | ||
createDispatchListener(instance, callback, lastCurrentTarget), | ||
); | ||
} | ||
if (eventHandlerListeners !== null) { | ||
eventHandlerListeners.forEach(entry => { | ||
if (entry.type === targetType && entry.capture === inCapturePhase) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same change as above. |
||
listeners.push( | ||
createDispatchListener( | ||
instance, | ||
entry.callback, | ||
(lastHostComponent: any), | ||
), | ||
); | ||
} | ||
} | ||
}); | ||
} | ||
} | ||
// If we are only accumulating events for the target, then we don't | ||
|
@@ -844,8 +822,8 @@ export function accumulateTwoPhaseListeners( | |
dispatchQueue: DispatchQueue, | ||
event: ReactSyntheticEvent, | ||
): void { | ||
const bubbled = event._reactName; | ||
const captured = bubbled !== null ? bubbled + 'Capture' : null; | ||
const bubbleName = event._reactName; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just for consistency. |
||
const captureName = bubbleName !== null ? bubbleName + 'Capture' : null; | ||
const listeners: Array<DispatchListener> = []; | ||
let instance = targetFiber; | ||
|
||
|
@@ -856,16 +834,16 @@ export function accumulateTwoPhaseListeners( | |
if (tag === HostComponent && stateNode !== null) { | ||
const currentTarget = stateNode; | ||
// Standard React on* listeners, i.e. onClick prop | ||
if (captured !== null) { | ||
const captureListener = getListener(instance, captured); | ||
if (captureName !== null) { | ||
const captureListener = getListener(instance, captureName); | ||
if (captureListener != null) { | ||
listeners.unshift( | ||
createDispatchListener(instance, captureListener, currentTarget), | ||
); | ||
} | ||
} | ||
if (bubbled !== null) { | ||
const bubbleListener = getListener(instance, bubbled); | ||
if (bubbleName !== null) { | ||
const bubbleListener = getListener(instance, bubbleName); | ||
if (bubbleListener != null) { | ||
listeners.push( | ||
createDispatchListener(instance, bubbleListener, currentTarget), | ||
|
@@ -1026,20 +1004,14 @@ export function accumulateEventHandleNonManagedNodeListeners( | |
|
||
const eventListeners = getEventHandlerListeners(currentTarget); | ||
if (eventListeners !== null) { | ||
const listenersArr = Array.from(eventListeners); | ||
const targetType = ((event.type: any): DOMEventName); | ||
|
||
for (let i = 0; i < listenersArr.length; i++) { | ||
const listener = listenersArr[i]; | ||
const {callback, capture: isCapturePhaseListener, type} = listener; | ||
if (type === targetType) { | ||
if (inCapturePhase && isCapturePhaseListener) { | ||
listeners.push(createDispatchListener(null, callback, currentTarget)); | ||
} else if (!inCapturePhase && !isCapturePhaseListener) { | ||
listeners.push(createDispatchListener(null, callback, currentTarget)); | ||
} | ||
eventListeners.forEach(entry => { | ||
if (entry.type === targetType && entry.capture === inCapturePhase) { | ||
listeners.push( | ||
createDispatchListener(null, entry.callback, currentTarget), | ||
); | ||
} | ||
} | ||
}); | ||
} | ||
if (listeners.length !== 0) { | ||
dispatchQueue.push(createDispatchEntry(event, listeners)); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just renames for clarity.