Reputation: 5514
Right now, this is the way I understand event handling in React:
document
layerEventPluginHub
SyntheticEvent
If my understanding of React internals is correct, and taking into account this part of the HTML spec:
Event objects are dispatched to an event target. But before dispatch can begin, the event object’s propagation path must first be determined.
Does React wait until the event bubbles up to document
to create its SyntheticEvent
? And if so, why? In the first step of the event's life, all info about its propagation path is known, so they could do it there.
Upvotes: 11
Views: 1312
Reputation: 49
React does top level delegation by adding all the react event handlers on document. So the following happens during capture and bubble phase.
Capturing Phase:
Bubbling Phase:
Upvotes: 0
Reputation: 5514
There is no mention in the React source of the reason for waiting for events to bubble in order to dispatch SyntheticEvent
s (that I can find).
The decision to wait for events that bubble to finish their bubbling before trapping them in the React events system is apparently only a matter of code organization.
Reading React's ReactBrowserEventEmitter listenTo
function, simplified here to make it more easily understandable:
for (let i = 0; i < dependencies.length; i++) {
const dependency = dependencies[i];
switch (dependency) {
case: // All event types that do NOT bubble
trapCapturedEvent(dependency, mountAt);
default:
// By default, listen on the top level to all non-media events.
// Media events don't bubble so adding the listener wouldn't do anything.
const isMediaEvent = mediaEventTypes.indexOf(dependency) !== -1;
if (!isMediaEvent) {
trapBubbledEvent(dependency, mountAt);
}
break;
}
isListening[dependency] = true;
}
The easiest way to separate events that bubble from those that don't (and keep code readable) seems to be to trap events that bubble in their bubbling phase, and those that don't in the capture phase.
So, in summary, the actual way events work (to make an analogy with my initial proposition in the question) is the following:
addEventListener(type, handler, false) // False stands for "catch it in the bubbling phase"
ReactBrowserEventEmitter
addEventListener(type, handler, true) // True stands for "catch it in the capture phase"
ReactBrowserEventEmitter
Upvotes: 1
Reputation: 9246
One of the reasons is performance. Rather than attaching event listeners to every event you use inside the app, React attaches a single event listener for every event type to document
. Then, when you create a, for example, onClick
event on a div
, it just adds that to it's internal event listener map. Once click on a div happens, React's listener on document node finds your event listener and calls it with SyntheticEvent.
This way, react doesn't have to re-create and clear listeners from DOM all the time, it just changes it's internal listener registry.
The other reason is that React's event bubbling works differently compared to DOM one in some cases. Portals in particular.
Take this for example:
const MyComponent = () => (
<div onClick={() => console.log('I was clicked!')}>
MyComponent
<SomeModalThatUsesPortalComponent>A modal</SomeModalThatUsesPortalComponent>
</div>
);
const SomeModalThatUsesPortalComponent = () => {
return ReactDOM.createPortal(
<div onClick={() => console.log('Modal clicked!')}>this.props.children</div>,
document.getElementById('myModalsPortal')
);
}
This would end up with following DOM:
<body>
<div>
My Component
</div>
<div id="myModalsPortal">
<div>A modal</div>
</div>
</body>
So in this case when using a Portal, DOM structure doesn't match the component structure exactly. Here is where bubbling behaviour diverges:
A modal
will not trigger a native click event on MyComponent's
divonClick
handler on MyComponent's
div, because modal is a child component of MyComponent
Upvotes: 4