Как определить элемент, по которому был произведен клик при делегировании событий в JavaScript
Что проверяют
Знаете ли вы разницу между event.target и event.currentTarget и понимаете ли, что target часто не тот элемент, который вам нужен. Следом спрашивают, какие события не всплывают и что ломает делегирование.
Как отвечать
При делегировании обработчик висит на контейнере, а событие приходит к нему всплытием. В обработчике доступны два разных элемента:
event.target: самый глубокий элемент, по которому реально кликнули. Это может быть иконкаsvgилиspanвнутри кнопки;event.currentTarget: элемент, на котором висит обработчик, то есть сам контейнер. У обычной функции то же значение лежит вthis, у стрелочной нет.
Поэтому нужный элемент ищут подъёмом от target:
list.addEventListener('click', (event) => {
const item = event.target.closest('[data-id]');
if (!item || !list.contains(item)) return;
handle(item.dataset.id, item.dataset.action);
});closest: проверяет сам элемент и всех предков по селектору, поэтому клик по иконке внутри кнопки находит кнопку;contains: страхует от случая, когдаclosestподнялся выше контейнера и нашёл элемент снаружи;- *`data-
иdataset`**: удобный способ передать идентификатор и действие без поиска по классам.
В TypeScript event.target имеет тип EventTarget | null, поэтому перед closest нужна проверка instanceof Element.
Полезно добавить: focus и blur не всплывают, для делегирования есть focusin и focusout, а mouseenter и mouseleave не всплывают вовсе. Для событий из Shadow DOM снаружи target указывает на хост компонента, реальный путь даёт event.composedPath(). React с 17-й версии сам вешает обработчики на корневой контейнер.
Где спотыкаются
Проверяют event.target.matches('.item'). Пока внутри кнопки только текст, это работает, а после добавления иконки клики по ней перестают обрабатываться.
Путают target и currentTarget и читают data-id не у того элемента, получая undefined.
Забывают, что stopPropagation во вложенном обработчике обрывает всплытие. Делегированный обработчик выше просто не вызывается, и такой баг долго ищут не там.