Когда мы устанавливаем обработчик событий для компонента, React не привязывает напрямую обработчик событий к элементу DOM.React настроил внутреннюю систему событий и унифицированную подписку на события и их распространение в этой системе.
В частности, React использует механизм делегирования событий для унифицированного мониторинга событий DOM в документе, а затем распределяет события по конкретным экземплярам компонента в соответствии с инициированной целью. Кроме того, приведенное выше e является синтетическим объектом события (SyntheticEvent), а не исходным объектом события DOM.
На момент написания этой статьи версия React — 16.8.6.
Так зачем настраивать систему событий?
Если вы знаете о Preact (автор написал статью доРазобрать исходный код Preact), Preact урезает многие элементы React, включая механизм событий, и Preact привязывает события непосредственно к элементам DOM.
Прежде чем изучать вещь, я должен сначала спросить, почему? Понимание его мотивов поможет вам получить существенное представление о нем.
Мотивация React для настройки системы событий выглядит следующим образом:
1. Расправьте различия совместимости между браузерами. Согласно React, это самая примитивная мотивация.Спецификация W3Cдля определения этих синтетических событий (SyntheticEvent), предназначенных для сглаживания различий между браузерами.
Кроме того, React также попытается смоделировать некоторые несовместимые события с низкой версией с помощью других связанных событий.Это исходное значение слова «синтез»? .
2. «Синтез» события, т. е. настройка события.. Помимо решения проблем совместимости, синтез событий также можно использовать для настройки расширенных событий, как правило, события React onChange, которое определяет событие изменения унифицированного значения для элементов формы. Кроме того, третьи стороны также могут синтезировать пользовательские события с помощью механизма подключаемых модулей событий React, хотя мало кто делает это.
3. Абстрактный кроссплатформенный механизм событий. Как и значение VirtualDOM, VirtualDOM абстрагирует кросс-платформенный метод рендеринга, поэтому цель соответствующего SyntheticEvent — предоставить абстрактный механизм кросс-платформенных событий.
4. React намерен сделать больше оптимизаций. Например, при использовании механизма делегирования событий большинство событий в конечном счете привязываются к документу, а не к самому узлу DOM.Это упрощает логику обработки событий DOM и снижает нагрузку на память.Но это также означает,React должен сам моделировать механизм всплытия событий..
5. React намерен вмешиваться в отправку событий. v16 представляет архитектуру Fiber.Чтобы оптимизировать интерактивный опыт пользователя, React будет вмешиваться в распределение событий. Различные типы событий имеют разные приоритеты.Например, события с высоким приоритетом могут прерывать отрисовку, позволяя пользовательскому коду своевременно реагировать на действия пользователя.
Хорошо, о реализации событий в React мы узнаем позже, я постараюсь не выкладывать код, а говорить блок-схемами.
основная концепция
общая архитектура
ReactEventListener- Обработчик событий.Привяжите сюда обработчик событий. Когда DOM инициирует событие, оно будет отправлено в дерево компонентов React отсюда.
ReactEventEmitter- Предоставьте интерфейс компонентному слою React для добавления подписок на события.
EventPluginHub- Как следует из названия, это «слот для плагинов», отвечающий за управление и регистрацию различных плагинов. Когда событие отправлено, плагин вызывается для создания синтетического события.
Plugin- Система событий React использует механизм плагинов для управления событиями для различных вариантов поведения. Эти плагины обрабатывают интересующие их типы событий и генерируют искусственные объекты событий. В настоящее время ReactDOM имеет следующие типы плагинов:
SimpleEventPlugin- Простые события, обрабатывают некоторые распространенные типы событий, такие как щелчок, ввод, keyDown, mouseOver, mouseOut, pointerOver, pointerOut
EnterLeaveEventPlugin- События mouseEnter/mouseLeave и pointerEnter/pointerLeave являются особыми и*over/*outПо сравнению с событиями они не поддерживают всплытие событий,*enterотправит событие всем входящим элементам, поведение чем-то похоже на:hover; а также*overПосле ввода элемента он также будет пузыриться, чтобы уведомить своего вышестоящего.Вы можете пройти этоПримерОбратите внимание на разницу между enter и over.
Если древовидная иерархия глубокая, большое количество триггеров mouseenter может вызвать проблемы с производительностью. Кроме того, он не поддерживает всплытие и не может идеально отслеживаться и распределяться в Document, поэтому ReactDOM использует*over/*outсобытия для имитации этих*enter/*leave.
ChangeEventPlugin- Событие изменения — это специальное событие для React, предназначенное для нормализации события изменения элементов формы.
Он поддерживает следующие элементы формы: input, textarea, select
SelectEventPlugin- Как и событие change, React нормализует событие select (изменение диапазона выбора) для элементов формы, применимое к элементам ввода, textarea и contentEditable.
BeforeInputEventPlugin- событие перед входом иcompositionобработка событий.
В этой статье основное внимание будет уделеноSimpleEventPluginРеализация , заинтересованные читатели могут самостоятельно прочитать исходный код React.
EventPropagatorsПросмотрите дерево компонентов React и соберите обработчики событий для всех компонентов, следуя двум этапам распространения событий DOM.
EventBatchingОтвечает за пакетное выполнение очередей событий и обработчиков событий, а также за обработку всплывающих событий.
SyntheticEventЭто базовый класс для «синтетических» событий, которые могут соответствовать объекту DOM Event. Однако, чтобы уменьшить потребление памяти и сборку мусора, React использует пул объектов для создания и выпуска объектов событий, а это означает, что SyntheticEvent нельзя использовать для асинхронных ссылок, и он будет освобожден после синхронного выполнения обработчика событий.
SyntheticEvent также имеет подклассы, соответствующие определенным типам событий DOM:
SyntheticAnimationEvent
SyntheticClipboardEvent
SyntheticCompositionEvent
SyntheticDragEvent
SyntheticFocusEvent
SyntheticInputEvent
SyntheticKeyboardEvent
SyntheticMouseEvent
SyntheticPointerEvent
SyntheticTouchEvent
....
Классификация событий и приоритизация
SimpleEventPlugin делит типы событий на три категории, соответствующие разным приоритетам (приоритет от низкого к высокому):
DiscreteEventДискретные события, такие как размытие, фокус, щелчок, отправка, touchStart. Эти события запускаются дискретно.
UserBlockingEventСобытия блокировки пользователя, такие как touchMove, mouseMove, прокрутка, перетаскивание, перетаскивание и т. д. Эти события «блокируют» взаимодействие с пользователем.
ContinuousEventНепрерывные события. Такие как load, error, loadStart, abort, animationEnd, имеют наивысший приоритет, а это значит, что они должны выполняться синхронно немедленно, что и является значением Continuous, который может выполняться непрерывно без прерывания.
Может быть необходимо сначала понять приоритет планирования React (Schedule), чтобы понять разницу между этими тремя типами событий. На момент написания этой статьи React имеет 5 уровней приоритета:
Immediate- Задачи этого приоритета будут выполняться синхронно, либо будут выполняться немедленно и не могут быть прерваны
UserBlocking(время ожидания 250 мс) Эти задачи обычно являются результатом взаимодействия с пользователем и требуют немедленной обратной связи.
Normal(тайм-аут 5 с) для задач, которые не нужно ощущать немедленно, например, сетевых запросов.
Low(время ожидания 10 с) Эти задачи можно отложить, но в конечном итоге их следует выполнить. Например, уведомления об анализе.
Idle(без тайм-аута) некоторые ненужные задачи (например, такие как скрытый контент).
В настоящее время ContinuousEvent соответствует Немедленному приоритету, UserBlockingEvent соответствует UserBlocking (который необходимо включить вручную), а DiscreteEvent соответствует UserBlocking, но перед его выполнением будут выполняться другие дискретные задачи.
Эта статья не будет вписаться в детали архитектуры реагирования волокна, и заинтересованные читатели могут прочитать расширенный список чтения в конце статьи.
детали реализации
Теперь давайте перейдем к теме статьи, как в React реализован механизм событий? В основном делится на две части:связыватьа такжераспределение.
Как связаны события?
Чтобы потом не запутаться, необходимо сначала разобраться в протоколе плагина в механизме событий React. Структура каждого плагина следующая:
Выше перечислены три типичных модуля EventPlugins:
SimpleEventPlugin— Простые события лучше всего понятны, их поведение более общее, и нет никаких хитростей, таких как отсутствие поддержки всплытия событий, отсутствие поддержки привязки к документу и т. д. Имеет взаимно однозначное соответствие с нативными DOM-событиями, которым легче управлять.
EnterLeaveEventPlugin- Как видно из рисунка выше,mouseEnterа такжеmouseLeaveзависит отmouseoutа такжеmouseoverмероприятие. то есть*Enter/*LeaveСобытия в React выполняются через*Over/*Outсобытия для имитации. Преимущество этого в том, что вы можете делегировать мониторинг документа и избежать*Enter/*LeaveКакое-то странное и бесполезное поведение.
ChangeEventPlugin- onChange — это пользовательское событие React, видно, что оно использует различные собственные типы событий DOM для имитации события onChange.
Кроме того, каждый плагин также определяетextractEventsМетод, этот метод принимает имя события, собственный объект события DOM, элемент DOM, вызванный событием, и экземпляр компонента React, и возвращает синтетический объект события. Если он возвращает пустой результат, это означает отсутствие обработки. Подробности об extractEvents будут объяснены в следующий раздел.
Когда ReactDOM запустится, он будет отправлен наEventPluginHubЗарегистрируйте эти плагины:
Хорошо, вернемся к теме, как связаны события? Поставьте точку останова и посмотрите на стек вызовов:
Предыдущий стек вызовов о том, как обновлять и отображать дерево React, выходит за рамки этой статьи.Из стека вызовов видно, что React будет выполнять привязку событий при инициализации и обновлении свойств. Сначала взгляните на блок-схему, игнорируя беспорядочные переходы:
1. Привязка события происходит при инициализации и обновлении реквизита.. Во-первых, реагирование определит, будет ли элемент媒体类型,События типа мультимедиа нельзя прослушивать в документе, поэтому они будут привязаны непосредственно к элементу.
2. Наоборот, привязать его к документу, Здесь необходимы две части информации, одна из которых — «список зависимостей событий», упомянутый выше, напримерonMouseEnterполагатьсяmouseover/mouseout; Вторая — это «таблица событий с подпиской», поддерживаемая ReactBrowserEventEmitter.Обработчику событий нужно только один раз подписаться в Document, поэтому выделяйте много ресурсов по сравнению с каждым элементом..
Следующим шагом является установка обработчика событий в соответствии с «приоритетом» события и «фазой захвата» (независимо от того, является ли это захватом или нет).:
Процесс привязки событий относительно прост, давайте посмотрим, как распределяются события.
Как распределяются события?
Как обычно, сначала перейдите к блок-схеме:
планирование триггеров событий
через вышеуказанноеtrapEventForPluginEventSystemФункция может знать, что разные типы событий имеют разные обработчики событий, и разница между ними в том, что приоритет планирования разный:
В конечном итоге будут вызываться различные типы событий.dispatchEventфункция.dispatchEventполучит цель, вызванную событием, из собственного объекта события DOM, а затем получит связанный экземпляр узла React в соответствии с целью.
Затем (есть несколько шагов посередине, которые здесь игнорируются) вызоветEventPluginHubизrunExtractedPluginEventsInBatch, этот метод перебирает список плагинов для обработки событий, создавая список SyntheticEvents:
SimpleEventPluginизextractEventsВ основном сделайте следующие три вещи:
1️⃣ Определите конструктор SyntheticEvent в соответствии с типом события
2️⃣ Создайте объект SyntheticEvent.
3️⃣ Получить список обработчиков пользовательских событий в соответствии с порядком распространения событий DOM.
Чтобы избежать потери производительности (создание объектов и сборка мусора), вызванной частым созданием и выпуском объектов событий, React использует пул событий для управления объектами событий, и используемые объекты событий будут помещены обратно в пул для последующего повторного использования..
Это также означает,После синхронного выполнения обработчика события объект SyntheticEvent будет немедленно переработан., все свойства будут недействительными. Таким образом, объект события SyntheticEvent обычно не используется в асинхронной операции. Вы также можете сохранить ссылку на объект события:
передачаSyntheticEvent#persist()метод, который сообщает React не перерабатывать в пул объектов
прямая цитатаSyntheticEvent#nativeEvent, на nativeEvent можно ссылаться постоянно, но чтобы не нарушать абстракцию, рекомендуется не ссылаться на nativeEvent напрямую
После создания объекта SyntheticEvent вам необходимоПройдите по дереву компонентов, чтобы подписаться на событие обработчика событий пользователя.сейчас:
function accumulateTwoPhaseDispatchesSingle(event) {
// 以_targetInst为基点, 按照DOM事件传播的顺序遍历组件树
traverseTwoPhase(event._targetInst, accumulateDirectionalDispatches, event);
}
Метод обхода на самом деле очень прост:
export function traverseTwoPhase(inst, fn, arg) {
const path = [];
while (inst) { // 从inst开始,向上级回溯
path.push(inst);
inst = getParent(inst);
}
let i;
// 捕获阶段,先从最顶层的父组件开始, 向下级传播
for (i = path.length; i-- > 0; ) {
fn(path[i], 'captured', arg);
}
// 冒泡阶段,从inst,即事件触发点开始, 向上级传播
for (i = 0; i < path.length; i++) {
fn(path[i], 'bubbled', arg);
}
}
accumulateDirectionalDispatchesФункция просто определяет, есть ли у текущего узла соответствующий обработчик событий:
Например, в следующем дереве компонентов процесс обхода выглядит следующим образом:
наконец вычислено_dispatchListenersОчередь такая:[handleB, handleC, handleA]
пакетное выполнение
После обхода плагина выполнения вы получите список SyntheticEvents,runEventsInBatchзаключается в том, чтобы выполнять эти события в пакетах_dispatchListenersочередь событий
export function runEventsInBatch(
events: Array<ReactSyntheticEvent> | ReactSyntheticEvent | null,
) {
// ...
forEachAccumulated(processingEventQueue, executeDispatchesAndRelease);
}
// 👇
const executeDispatchesAndRelease = function(event: ReactSyntheticEvent) {
if (event) {
// 按顺序执行_dispatchListeners
// 👇
executeDispatchesInOrder(event);
// 如果没有调用persist()方法则直接回收
if (!event.isPersistent()) {
event.constructor.release(event);
}
}
};
export function executeDispatchesInOrder(event) {
// 遍历dispatchListeners
for (let i = 0; i < dispatchListeners.length; i++) {
// 通过调用 stopPropagation 方法可以禁止执行下一个事件处理器
if (event.isPropagationStopped()) {
break;
}
// 执行事件处理器
executeDispatch(event, dispatchListeners[i], dispatchInstances[i]);
}
}
ОК, механизм событий React в основном представлен здесь, вот только краткое введениеSimpleEventPlugin, В реальном коде много деталей обработки событий, из-за ограничений по объему в этой статье мы не будем вдаваться в подробности. Заинтересованные читатели могут сами ознакомиться с исходным кодом React.
будущее
Внутри React есть экспериментальный API событий, который вызывается внутри React.React Flare, официальное названиеreact-events, Этот API обеспечивает межплатформенную расширенную инкапсуляцию событий на разных устройствах..
react-events определяет концепцию **ответчиков событий**, которые могут захватывать события дерева подкомпонентов или корневого узла приложения, а затем преобразовывать их в настраиваемые события.
Типичными расширенными событиями являются жесты, такие как нажатие, длительное нажатие и пролистывание. Обычно нам нужно реализовать этот набор распознавания жестов самостоятельно или с помощью сторонних библиотек, таких как
Тогда цель реагирующих событийПредоставьте разработчикам набор общих механизмов событий для реализации инкапсуляции «расширенных событий» и даже для реализации кросс-платформенных и кросс-девайсных событий., теперь вы можете обернуть эти события жестов событиями реакции.
реакции-события в дополнение к основномуResponderИнтерфейс также инкапсулирует некоторые встроенные модули для реализации кросс-платформенной, часто используемой расширенной инкапсуляции событий:
Focus module
Hover module
Press module
FocusScope module
Input module
KeyBoard module
Drag module
Pan module
Scroll module
Swipe module
подниматьPressмодуль в качестве примера,Пресс-модульБудет реагировать на событие нажатия элемента, который он оборачивает. События нажатия включают onContextMenu, onLongPress, onPress, onPressEnd, onPressMove, onPressStart и т. д. Нижний слой преобразуется с помощью мыши, пера, касания, трекпада и других событий.
Схема работы реагирующих событий выглядит следующим образом:Ответчик событий (Event Responders) будет подключен к хост-узлу, он будет прослушивать собственные события (DOM или React Native), распространяемые хостом или дочерними узлами на хост-узле, и преобразовывать/объединять их в расширенные события.:
Вы можете играть с этим Codesanboxreact-events:
Предварительное исследование создания ответчика
Давайте выберем простой модуль, чтобы понять некоторые основные API-интерфейсы событий реакции. В настоящее время самым простым модулем является модуль клавиатуры. Цель модуля клавиатуры — стандартизировать ключевые свойства объектов событий keydown и keyup (поведение ключевых свойств некоторых браузеров отличается), его реализация выглядит следующим образом:
Читатели должны сейчасОтветственность ответчикаС некоторым базовым пониманием, он в основном делает следующие вещи:
Объявите собственные события (например, DOM) для прослушивания, как указано выше.targetEventTypes
Обработка и преобразование синтетических событий, как указано выше.onEvent
Создавайте и распространяйте пользовательские события. как указано вышеcontext.dispatchEvent
По сравнению с описанным выше модулем «Клавиатура», в реальности многие дополнительные события, такие как longPress, гораздо сложнее реализовать.условие, возможно, также придется отвечать исключительновладение(То есть только один Responder может одновременно обрабатывать события, что часто используется для мобильных сенсорных жестов, таких как React Native’sGestureResponderSystem).
В настоящее время react-events рассматривает эти сценарии, взгляните на обзор API:
Как упоминалось выше, событие React использует подключаемый механизм для реализации обработки и синтеза событий, обычно это событие onChange. Событие onChange на самом деле является так называемым «расширенным событием», которое моделируется различными собственными событиями компонента формы.
Другими словами, React может по существу инкапсулировать расширенные события с помощью механизма подключаемых модулей. Но если читатели заглянут в исходный код, они почувствуют, что логика внутри весьма извилиста и опирается на множество внутренних реализаций React.Так что этот внутренний механизм плагинов не для обычных разработчиков.
react-eventsИнтерфейс намного проще, он скрывает множество внутренних деталей и ориентирован на обычных разработчиков. Мы можем использовать его для достижения высокопроизводительного пользовательского распределения событий, и большее значение имеет то, что он может обеспечить обработку событий между платформами и устройствами.
В настоящее время react-events все еще находится в экспериментальной стадии, по умолчанию функция отключена, а API может измениться, поэтому не рекомендуется использовать ее в производственной среде. сквозь этоIssueследить за его ходом.
Наконец, похвалите инновационные способности команды React!