Как добавить обработчик события на DOM-элемент в React?
Что проверяют
Знаете ли вы, чем событие в React отличается от нативного и где проходит граница между декларативным onClick и ручным addEventListener. Следом обычно спрашивают, как передать аргумент в обработчик и когда без нативного слушателя не обойтись.
Как отвечать
Основной способ декларативный: обработчик передаётся пропсом в camelCase, onClick={handleClick}. Передаётся ссылка на функцию, а не результат вызова. Дальше детали, которые отличают сильный ответ:
- синтетическое событие: React оборачивает нативное событие в
SyntheticEventс единым API, оригинал доступен черезe.nativeEvent. С React 17 слушатели вешаются на корневой контейнер приложения, а не наdocument, и пулинг событий убран, поэтому объект события можно читать асинхронно; - отмена действия: только
e.preventDefault(),return falseв React не работает. Всплытие останавливаетe.stopPropagation(), фаза перехвата доступна черезonClickCapture; - аргументы:
onClick={() => remove(id)}илиdata-idна элементе и чтениеe.currentTarget.dataset.id. Стрелка создаёт новую функцию на каждый рендер, это важно, только если ребёнок обёрнут вmemo, тогда нуженuseCallback; - нативный слушатель: для
window,documentили стороннего DOM-узла используютuseEffectсaddEventListenerи обязательным снятием в функции очистки. Элемент получают черезuseRef.
Нативный путь нужен и там, где React ограничивает: onWheel, onTouchStart и onTouchMove он вешает пассивными, и preventDefault() внутри них не сработает. Тогда слушатель ставят вручную с { passive: false }.
Где спотыкаются
Пишут onClick={handleClick()}. Функция вызывается при рендере, а если внутри меняется состояние, получается бесконечный цикл перерисовок.
Вешают addEventListener в useEffect без функции очистки. В режиме разработки StrictMode монтирует эффект дважды, обработчик срабатывает два раза, а после размонтирования слушатель остаётся висеть.
Путают e.target и e.currentTarget: первый указывает на элемент, где случился клик, второй на элемент с обработчиком. Если в кнопке лежит иконка, dataset у target окажется пустым.