What is it about? Why and why?
And at the end: why don’t forget about delegation in React.
JavaScript and HTML interact with each other through events. Each event serves to tell JavaScript that something has happened in the document or browser window. In order to catch these events, we need listeners (listeners), such handlers that are triggered in the event of an event.
Order. Solving the problem: how to understand which part of the page the event belongs to? Two methods were implemented: in Internet Explorer, “event bubbling,” and in Netscape Communicator, “event hooking.”
In this case, the event is triggered at the deepest node in the document tree, after which it rises through the hierarchy to the window itself.
<!DOCTYPE html> <html> <head> <title>Some title</title> </head> <body> <div id="myDiv">Click Me</div> </body> </html>
In this case, there will be the following order:
Now surfing is supported by all modern browsers, albeit with different implementations.
In the case of event interception, the opposite is true:
It was thought that the event could be processed before it reached the target element (as decided in Netscape later picked up by all modern browsers).
As a result, we have the following structure for distributing DOM events:
This scheme is divided into three phases: the interception phase - the event can be intercepted before it hits the element, the target phase - processing by the target element and the ascent phase - to perform any final actions in response to the event.
Let's see a typical example of event handling in JavaScript.
const btn = document.getElementById('myDiv') btn.addEventListener("click", handler) // some code btn.removeEventListener("click", handler)
All would be nothing, but here we recall our beloved IE which subscribes to events using attachEvent, and to remove detachEvent. And you can subscribe to the event several times. And do not forget that by signing an anonymous function, we do not have the opportunity to unsubscribe.
But we are not g * extra coders. Let's do everything according to the canon:
var EventUtil = { addHandler: function (elem, type, handler) { if (elem.addEventListener) { elem.addEventListener(type, handler, false) } else if (elem.attachEvent) { elem.attachEvent("on" + type, handler) } else { elem["on" = type] = hendler } }, removeHandler: function (elem, type, handler) { if (elem.removeEventListener) { elem.removeEventListener(type, handler, false) } else if (elem.detachEvent) { elem.detachEvent("on" + type, handler) } else { elem["on" = type] = null } } }
So good, but what about the event object? After all, in IE there is no .target .srcElement, preventDefault? no returnValue = false. But there’s nothing to add a couple of methods:
var EventUtil = { addHandler: function (elem, type, handler) { if (elem.addEventListener) { elem.addEventListener(type, handler, false) } else if (elem.attachEvent) { elem.attachEvent("on" + type, handler) } else { elem["on" = type] = hendler } }, getEvent: function (event) { return event ? event : window.event }, getTarget: function (event) { return event.target || event.srcElement }, preventDefault: function (event) { if (event.preventDefault) { event.preventDefault() } else { event.returnValue = false } }, removeHandler: function (elem, type, handler) { if (elem.removeEventListener) { elem.removeEventListener(type, handler, false) } else if (elem.detachEvent) { elem.detachEvent("on" + type, handler) } else { elem["on" = type] = null } }, stopPropagation: function (event) { if (event.stopPropagation) { event.stopPropagation() } else { event.cancelBubble = true } } }
Etc. etc. and these are all dances.
Well we are well done, we solved all problems, everything is ok. True, the code came out rather cumbersome. Now imagine that we need a lot of subscriptions to many elements. Wow, this will take quite a few lines of code. Example:
<ul> <li id="id1">go somewhere</li> <li id="id2">do something</li> <li id="some-next-id">next</li> </ul> var item1 = document.getElementById('id1') var item2 = document.getElementById('id2') var itemNext = document.getElementById('some-next-id') EventUtil.addHandler(item1, "click", someHandle) EventUtil.addHandler(item2, "click", someHandle2) EventUtil.addHandler(itemNext, "click", someHandle3)
And so for each element, and you must not forget to delete, work with target and the like
All we need to do is connect one single handler to the highest point in the DOM tree:
<ul id="main-id"> // id <li id="id1">go somewhere</li> <li id="id2">do something</li> <li id="some-next-id">next</li> </ul> var list = document.getElementById('main-id') EventUtil.addHandler(list, "click", function(event) { event = EventUtil.getEvent(event) var target = EventUtil.getTarget(event) switch(target.id) { case "id1": // - id1 break case "id2": // - id1 break case "some-next-id": // - break } })
As a result, we have only one handler in memory, and for the desired action, you can use the id property. Less memory consumption improves overall page performance. Registering an event handler requires less time and fewer calls to the DOM. The exception is perhaps mouseover and mouseout, with them everything is a little more complicated.
As for cross-browser compatibility, the guys from facebook have already done everything for us. All our event handlers receive an instance of SyntheticEvent . Which takes care of us by reusing events from the pool, removing all properties after calling the handler.
Good.
Nevertheless, an extra handler is an extra handler. I met several times, and I repent myself, I wrote this kind of code:
class Example extends React.Component { handleClick () { console.log('click') } render () { return ( <div> {new Array(20).fill().map((_, index) => <div key={index} // elem.id id={index} // elem.id onClick={() => console.log('click')} /> )} </div> ) } }
The example shows the case when there is some sheet with n-number of elements, and therefore with n-number of registrations of handlers.
Let’s run, go to the page and check how many handlers are in action right now. For this, I found a good script:
Array.from(document.querySelectorAll('*')) .reduce(function(pre, dom){ var clks = getEventListeners(dom).click; pre += clks ? clks.length || 0 : 0; return pre }, 0)
Works in chrome dev-tool.
And now we delegate all this to the parent div and cheers, we just optimized our application n = array.length times. Example code below:
class Example extends React.Component { constructor () { super() this.state = { useElem: 0 } } handleClick (elem) { var id = elem.target.id this.setState({ useElem: id }) } render () { return ( <div onClick={this.handleClick}> {new Array(20).fill().map((_, index) => <div key={index} // elem.id id={index} // elem.id useElem={index === this.state.useElem} /> )} </div> ) } }
Delegation is a good tool for processing a large number of subscriptions, and in the case of dynamic rendering and frequent redraws, it is simply irreplaceable. Pity the user's resources, they are not unlimited.
Written from a JavaScript book for professional web developers, by Nicholas Zakas.
Thanks a lot for your attention. If you have something to share or if you find some kind of flaw, maybe there is a mistake or just have a question, then write in the comments. I will be glad to any feedback!