We forgot about delegation in JavaScript. Event delegation in react

Hello. This article is about event delegation in JavaScript and its implementation in react.js.









What is it about? Why and why?







To get started, let's briefly discuss:



  1. what is an event;
  2. how distribution occurs;
  3. Processing DOM Level 2 with an example in JavaScript;


And at the end: why don’t forget about delegation in React.







Event



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.







Event Propagation



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.”







Event bubbling



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:







  1. div element
  2. body element
  3. html element
  4. document
  5. window


Now surfing is supported by all modern browsers, albeit with different implementations.

In the case of event interception, the opposite is true:







  1. window
  2. document
  3. html element
  4. body element
  5. div element


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:







  1. window
  2. document
  3. html element
  4. body element // the interception phase ends
  5. div element // target phase
  6. body element // the ascent phase begins
  7. html element
  8. document
  9. window


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.







So, we turn to event processing



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







And here event delegation comes to our aid.



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.







Now what about React



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!








All Articles