Все началось, когда я попытался перенаправить отправленные обработчики событий React в другой элемент DOM. Я не буду вдаваться в подробности относительно варианта использования, но то, что я сделал, было довольно логичным: я переопределил метод addEventListener() в экземпляре элемента DOM, надеясь зафиксировать отправленные аргументы и поступить с ними, как я хочу. К сожалению, не сработало ...

Как придешь?! Как могло случиться так, что React обрабатывает события без вызова метода addEventListener()? В конце концов, он доказал свою эффективность во многих приложениях.

Верно, но это не то, что вы думаете. Сначала я хотел бы, чтобы вы сделали снимок реализации ReactDOM. На самом деле у него есть комментарий, который объясняет всю систему обработки событий:

Summary of `ReactBrowserEventEmitter` event handling:
  - Top-level delegation is used to trap most native browser events. This may only occur in the main thread and is the responsibility of ReactDOMEventListener, which is injected and can therefore support pluggable event sources. This is the only work that occurs in the main thread.
  - We normalize and de-duplicate events to account for browser quirks. This may be done in the worker thread.
  - Forward these native events (with the associated top-level type used to trap it) to `EventPluginHub`, which in turn will ask plugins if they want to extract any synthetic events.
  - The `EventPluginHub` will then process each event by annotating them with "dispatches", a sequence of listeners and IDs that care about that event.
  - The `EventPluginHub` then dispatches the events.

Источник: src / events / ReactBrowserEventEmitter.js: 32

Вначале я увидел вот что:

Но после небольшой отладки, просмотра трассировки стека и некоторой документации React все стало намного яснее. Тогда давайте разберемся с этим и попробуем упростить.

Top-level delegation is used to trap most native browser events. This may only occur in the main thread and is the responsibility of
ReactDOMEventListener, which is injected and can therefore support
pluggable event sources. This is the only work that occurs in the main thread.

React использует один прослушиватель событий для каждого типа события, чтобы вызывать все отправленные обработчики в виртуальной DOM. Например, учитывая следующий компонент React:

У нас будет единственный прослушиватель событий, зарегистрированный в собственном DOM для события click. Запустив метод getEventListeners(), доступный в инструментах разработчика Chrome, мы получим следующий результат:

{click: Array(1)}

Каждый прослушиватель событийного типа будет обеспечиваться на один цикл рендеринга, поэтому, если бы мы должны были определить дополнительные обработчики событий keydowntype, мы получили бы следующий вывод:

{click: Array(1), keydown: Array(1)}

Источник: пакеты / response-dom / src / client / ReactDOMComponent.js: 225

We normalize and de-duplicate events to account for browser quirks. This may be done in the worker thread.

Для каждого браузера, независимо от его реализации, у нас будут согласованные аргументы событий, поскольку React нормализует их. Независимо от того, используем ли мы последнюю версию браузера Chrome или IE8, аргументы события click будут выглядеть так:

  • логическое altKey
  • Кнопка номер
  • числовые кнопки
  • номер clientX
  • номер clientY
  • логическое ctrlKey
  • логическое getModifierState (ключ)
  • логическое metaKey
  • номер стр.X
  • номер pageY
  • DOMEventTarget relatedTarget
  • номер экранX
  • номер экранY
  • логическое shiftKey

Документы: события: поддерживаемые-события
Источник: пакеты / response-dom / src / events / SimpleEventPlugin.js: 259

Поскольку React регистрирует один прослушиватель событий для нескольких обработчиков, ему потребуется повторно отправить событие для каждого обработчика.

Источник: EventPluginHub.js: 168

Forward these native events (with the associated top-level type used to trap it) to `EventPluginHub`, which in turn will ask plugins if they want to extract any synthetic events.

EventPluginHub - это центральный компонент системы обработки событий React. Это то, что объединяет все плагины событий в одном месте и перенаправляет отправленные события каждому из них. Каждый плагин отвечает за извлечение и обработку различных типов событий, например, у нас есть SimpleEventPlugin, который будет обрабатывать события, которые, вероятно, будут реализованы в большинстве браузеров, такие как события мыши и нажатия клавиш (источник); у нас также есть ChangeEventPlugin, который будет обрабатывать очень известное событие onChange (источник).

Источник: пакеты / события / EventPluginHub.js: 168

Синтетические события - это нормализованные аргументы событий React, которые обеспечивают согласованность во всех браузерах и генерируются подключаемыми модулями. Обратите внимание, что синтетические события объединяются! Это означает, что один и тот же экземпляр объекта используется в нескольких обработчиках, только он сбрасывается с новыми свойствами перед каждым вызовом, а затем удаляется:

Документы: события: объединение событий
Источник: пакеты / response-dom / src / events / SimpleEventPlugin.js: 322

The `EventPluginHub` will then process each event by annotating them with "dispatches", a sequence of listeners and IDs that care about that event.

Как уже упоминалось, каждое событие может иметь несколько обработчиков, даже если каждое из них фактически прослушивается один раз реальной DOM. Соответственно, соответствующие «диспетчеры», которые состоят из обработчиков событий и соответствующих им волоконных узлов (узлов в виртуальном дереве DOM), должны накапливаться для будущего использования.

Источник: пакеты / события / EventPropagators.js: 90

The `EventPluginHub` then dispatches the events.

Плагин-концентратор просматривает накопленную информацию и отправляет события, вызывая тем самым отправленные обработчики событий.

Источник: пакеты / события / EventPluginUtils.js: 77

Вот как вкратце работает эта система обработки событий. Я хотел бы отметить несколько моментов:

  • Слушатели событий верхнего уровня, которые зарегистрированы в основной DOM (window.document), также могут быть зарегистрированы в других DOM, в зависимости от того, где находится контейнер приложения. Например, если контейнер принят iframe, то DOM iframe будет основным прослушивателем событий; это также может быть фрагмент документа, теневой DOM и т. д. Важно, чтобы вы знали об этом и знали, что существует небольшое ограничение на распространение событий.
  • React повторно отправляет события в два этапа: один для захвата, а другой для всплытия, точно так же, как это делает собственный DOM.
  • Обработка событий, которая выполняется в React Native, отличается от обработки событий в React DOM, и вы не должны путать их! React - это просто библиотека, которая создает виртуальное представление представления, которое мы хотели бы отобразить, а React DOM / Native - это мост между React и средой, которую мы используем. Эта статья актуальна только для React DOM!

В конце концов, вы по-прежнему сможете использовать React с этой информацией или без нее, но я думаю, что широко используемая библиотека, такая как React, заслуживает большего внимания, особенно если вы хотите улучшить свою игру.

Итак, возвращаясь к тому, что заставило меня написать эту статью, если я хотел перенаправить зарегистрированный React, все, что мне нужно было сделать, это переопределить addEventListener() для DOM, а не соответствующий Node. Конечно, перезапись собственного метода - это НЕ то, что нужно делать, и это очень плохая практика (* кашель, кашель * Zone.js), но я не буду вдаваться в подробности моего конкретного использования, так как это тема для другой статьи.

Обновление: (21 ноября 2018 г.)

Тем, кому понравилась эта статья и то, как я анализирую реализацию React, рекомендую прочитать мою статью о React Hooks и о том, как они работают под капотом.