Когда мы устанавливаем обработчик событий для компонента, 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. Структура каждого плагина следующая:
export type EventTypes = {[key: string]: DispatchConfig};
// 插件接口
export type PluginModule<NativeEvent> = {
eventTypes: EventTypes, // 声明插件支持的事件类型
extractEvents: ( // 对事件进行处理,并返回合成事件对象
topLevelType: TopLevelType,
targetInst: null | Fiber,
nativeEvent: NativeEvent,
nativeEventTarget: EventTarget,
) => ?ReactSyntheticEvent,
tapMoveThreshold?: number,
};
eventTypesОбъявить тип события, за которое отвечает плагин, что делается черезDispatchConfig
описать:
export type DispatchConfig = {
dependencies: Array<TopLevelType>, // 依赖的原生事件,表示关联这些事件的触发. ‘简单事件’一般只有一个,复杂事件如onChange会监听多个, 如下图👇
phasedRegistrationNames?: { // 两阶段props事件注册名称, React会根据这些名称在组件实例中查找对应的props事件处理器
bubbled: string, // 冒泡阶段, 如onClick
captured: string, // 捕获阶段,如onClickCapture
},
registrationName?: string // props事件注册名称, 比如onMouseEnter这些不支持冒泡的事件类型,只会定义 registrationName,不会定义phasedRegistrationNames
eventPriority: EventPriority, // 事件的优先级,上文已经介绍过了
};
Взгляните на пример:
Выше перечислены три типичных модуля 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
Зарегистрируйте эти плагины:
EventPluginHubInjection.injectEventPluginsByName({
SimpleEventPlugin: SimpleEventPlugin,
EnterLeaveEventPlugin: EnterLeaveEventPlugin,
ChangeEventPlugin: ChangeEventPlugin,
SelectEventPlugin: SelectEventPlugin,
BeforeInputEventPlugin: BeforeInputEventPlugin,
});
Хорошо, вернемся к теме, как связаны события? Поставьте точку останова и посмотрите на стек вызовов:
Предыдущий стек вызовов о том, как обновлять и отображать дерево React, выходит за рамки этой статьи.Из стека вызовов видно, что React будет выполнять привязку событий при инициализации и обновлении свойств. Сначала взгляните на блок-схему, игнорируя беспорядочные переходы:
-
1. Привязка события происходит при инициализации и обновлении реквизита.. Во-первых, реагирование определит, будет ли элемент
媒体类型
,События типа мультимедиа нельзя прослушивать в документе, поэтому они будут привязаны непосредственно к элементу. -
2. Наоборот, привязать его к документу, Здесь необходимы две части информации, одна из которых — «список зависимостей событий», упомянутый выше, например
onMouseEnter
полагатьсяmouseover/mouseout
; Вторая — это «таблица событий с подпиской», поддерживаемая ReactBrowserEventEmitter.Обработчику событий нужно только один раз подписаться в Document, поэтому выделяйте много ресурсов по сравнению с каждым элементом..
Код примерно такой:
export function listenTo(
registrationName: string, // 注册名称,如onClick
mountAt: Document | Element | Node, // 组件树容器,一般是Document
): void {
const listeningSet = getListeningSetForElement(mountAt); // 已订阅事件表
const dependencies = registrationNameDependencies[registrationName]; // 事件依赖
for (let i = 0; i < dependencies.length; i++) {
const dependency = dependencies[i];
if (!listeningSet.has(dependency)) { // 未订阅
switch (dependency) {
// ... 特殊的事件监听处理
default:
const isMediaEvent = mediaEventTypes.indexOf(dependency) !== -1;
if (!isMediaEvent) {
trapBubbledEvent(dependency, mountAt); // 设置事件处理器
}
break;
}
listeningSet.add(dependency); // 更新已订阅表
}
}
}
- Следующим шагом является установка обработчика событий в соответствии с «приоритетом» события и «фазой захвата» (независимо от того, является ли это захватом или нет).:
function trapEventForPluginEventSystem(
element: Document | Element | Node, // 绑定到元素,一般是Document
topLevelType: DOMTopLevelEventType, // 事件名称
capture: boolean,
): void {
let listener;
switch (getEventPriority(topLevelType)) {
// 不同优先级的事件类型,有不同的事件处理器进行分发, 下文会详细介绍
case DiscreteEvent: // ⚛️离散事件
listener = dispatchDiscreteEvent.bind(
null,
topLevelType,
PLUGIN_EVENT_SYSTEM,
);
break;
case UserBlockingEvent: // ⚛️用户阻塞事件
listener = dispatchUserBlockingUpdate.bind(
null,
topLevelType,
PLUGIN_EVENT_SYSTEM,
);
break;
case ContinuousEvent: // ⚛️可连续事件
default:
listener = dispatchEvent.bind(null, topLevelType, PLUGIN_EVENT_SYSTEM);
break;
}
const rawEventName = getRawEventName(topLevelType);
if (capture) { // 绑定事件处理器到元素
addEventCaptureListener(element, rawEventName, listener);
} else {
addEventBubbleListener(element, rawEventName, listener);
}
}
Процесс привязки событий относительно прост, давайте посмотрим, как распределяются события.
Как распределяются события?
Как обычно, сначала перейдите к блок-схеме:
планирование триггеров событий
через вышеуказанноеtrapEventForPluginEventSystem
Функция может знать, что разные типы событий имеют разные обработчики событий, и разница между ними в том, что приоритет планирования разный:
// 离散事件
// discrentUpdates 在UserBlocking优先级中执行
function dispatchDiscreteEvent(topLevelType, eventSystemFlags, nativeEvent) {
flushDiscreteUpdatesIfNeeded(nativeEvent.timeStamp);
discreteUpdates(dispatchEvent, topLevelType, eventSystemFlags, nativeEvent);
}
// 阻塞事件
function dispatchUserBlockingUpdate(
topLevelType,
eventSystemFlags,
nativeEvent,
) {
// 如果开启了enableUserBlockingEvents, 则在UserBlocking优先级中调度,
// 开启enableUserBlockingEvents可以防止饥饿问题,因为阻塞事件中有scroll、mouseMove这类频繁触发的事件
// 否则同步执行
if (enableUserBlockingEvents) {
runWithPriority(
UserBlockingPriority,
dispatchEvent.bind(null, topLevelType, eventSystemFlags, nativeEvent),
);
} else {
dispatchEvent(topLevelType, eventSystemFlags, nativeEvent);
}
}
// 可连续事件则直接同步调用dispatchEvent
В конечном итоге будут вызываться различные типы событий.dispatchEvent
функция.dispatchEvent
получит цель, вызванную событием, из собственного объекта события DOM, а затем получит связанный экземпляр узла React в соответствии с целью.
export function dispatchEvent(topLevelType: DOMTopLevelEventType, eventSystemFlags: EventSystemFlags, nativeEvent: AnyNativeEvent): void {
// 获取事件触发的目标DOM
const nativeEventTarget = getEventTarget(nativeEvent);
// 获取离该DOM最近的组件实例(只能是DOM元素组件)
let targetInst = getClosestInstanceFromNode(nativeEventTarget);
// ....
dispatchEventForPluginEventSystem(topLevelType, eventSystemFlags, nativeEvent, targetInst);
}
Затем (есть несколько шагов посередине, которые здесь игнорируются) вызоветEventPluginHub
изrunExtractedPluginEventsInBatch
, этот метод перебирает список плагинов для обработки событий, создавая список SyntheticEvents:
export function runExtractedPluginEventsInBatch(
topLevelType: TopLevelType,
targetInst: null | Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: EventTarget,
) {
// 遍历插件列表, 调用插件的extractEvents,生成SyntheticEvent列表
const events = extractPluginEvents(
topLevelType,
targetInst,
nativeEvent,
nativeEventTarget,
);
// 事件处理器执行, 见后文批量执行
runEventsInBatch(events);
}
Как плагин обрабатывает события?
Теперь, чтобы увидеть, как плагин обрабатывает события, начнем сSimpleEventPlugin
Например:
const SimpleEventPlugin: PluginModule<MouseEvent> & {
getEventPriority: (topLevelType: TopLevelType) => EventPriority,
} = {
eventTypes: eventTypes,
// 抽取事件对象
extractEvents: function(
topLevelType: TopLevelType,
targetInst: null | Fiber,
nativeEvent: MouseEvent,
nativeEventTarget: EventTarget,
): null | ReactSyntheticEvent {
// 事件配置
const dispatchConfig = topLevelEventsToDispatchConfig[topLevelType];
// 1️⃣ 根据事件类型获取SyntheticEvent子类事件构造器
let EventConstructor;
switch (topLevelType) {
// ...
case DOMTopLevelEventTypes.TOP_KEY_DOWN:
case DOMTopLevelEventTypes.TOP_KEY_UP:
EventConstructor = SyntheticKeyboardEvent;
break;
case DOMTopLevelEventTypes.TOP_BLUR:
case DOMTopLevelEventTypes.TOP_FOCUS:
EventConstructor = SyntheticFocusEvent;
break;
// ... 省略
case DOMTopLevelEventTypes.TOP_GOT_POINTER_CAPTURE:
// ...
case DOMTopLevelEventTypes.TOP_POINTER_UP:
EventConstructor = SyntheticPointerEvent;
break;
default:
EventConstructor = SyntheticEvent;
break;
}
// 2️⃣ 构造事件对象, 从对象池中获取
const event = EventConstructor.getPooled(
dispatchConfig,
targetInst,
nativeEvent,
nativeEventTarget,
);
// 3️⃣ 根据DOM事件传播的顺序获取用户事件处理器
accumulateTwoPhaseDispatches(event);
return event;
},
};
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
Функция просто определяет, есть ли у текущего узла соответствующий обработчик событий:
function accumulateDirectionalDispatches(inst, phase, event) {
// 检查是否存在事件处理器
const listener = listenerAtPhase(inst, event, phase);
// 所有处理器都放入到_dispatchListeners队列中,后续批量执行这个队列
if (listener) {
event._dispatchListeners = accumulateInto(
event._dispatchListeners,
listener,
);
event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
}
}
Например, в следующем дереве компонентов процесс обхода выглядит следующим образом:
наконец вычислено_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 определяет концепцию **ответчиков событий**, которые могут захватывать события дерева подкомпонентов или корневого узла приложения, а затем преобразовывать их в настраиваемые события.
Типичными расширенными событиями являются жесты, такие как нажатие, длительное нажатие и пролистывание. Обычно нам нужно реализовать этот набор распознавания жестов самостоятельно или с помощью сторонних библиотек, таких как
import Gesture from 'rc-gesture';
ReactDOM.render(
<Gesture
onTap={handleTap}
onSwipe={onSwipe}
onPinch={handlePinch}
>
<div>container</div>
</Gesture>,
container);
Тогда цель реагирующих событийПредоставьте разработчикам набор общих механизмов событий для реализации инкапсуляции «расширенных событий» и даже для реализации кросс-платформенных и кросс-девайсных событий., теперь вы можете обернуть эти события жестов событиями реакции.
реакции-события в дополнение к основному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 и т. д. Нижний слой преобразуется с помощью мыши, пера, касания, трекпада и других событий.
Взгляните на пример использования:
import { PressResponder, usePressListener } from 'react-events/press';
const Button = (props) => (
const listener = usePressListener({ // ⚛️ 通过hooks创建Responder
onPressStart,
onPress,
onPressEnd,
})
return (
<div listeners={listener}>
{subtrees}
</div>
);
);
Схема работы реагирующих событий выглядит следующим образом:Ответчик событий (Event Responders) будет подключен к хост-узлу, он будет прослушивать собственные события (DOM или React Native), распространяемые хостом или дочерними узлами на хост-узле, и преобразовывать/объединять их в расширенные события.:
Предварительное исследование создания ответчика
Давайте выберем простой модуль, чтобы понять некоторые основные API-интерфейсы событий реакции. В настоящее время самым простым модулем является модуль клавиатуры. Цель модуля клавиатуры — стандартизировать ключевые свойства объектов событий keydown и keyup (поведение ключевых свойств некоторых браузеров отличается), его реализация выглядит следующим образом:
/**
* 定义Responder的实现
*/
const keyboardResponderImpl = {
/**
* 1️⃣定义Responder需要监听的子树的DOM事件,对于Keyboard来说是['keydown', 'keyup';]
*/
targetEventTypes,
/**
* 2️⃣监听子树触发的事件
*/
onEvent(
event: ReactDOMResponderEvent, // 包含了当前触发事件的相关信息,如原生事件对象,事件触发的节点,事件类型等等
context: ReactDOMResponderContext, // Responder的上下文,给Responder提供了一些方法来驱动事件分发
props: KeyboardResponderProps, // 传递给Responder的props
): void {
const {responderTarget, type} = event;
if (props.disabled) {
return;
}
if (type === 'keydown') {
dispatchKeyboardEvent(
'onKeyDown',
event,
context,
'keydown',
((responderTarget: any): Element | Document),
);
} else if (type === 'keyup') {
dispatchKeyboardEvent(
'onKeyUp',
event,
context,
'keyup',
((responderTarget: any): Element | Document),
);
}
},
};
Давайте посмотрим на dispatchKeyboardEvent:
function dispatchKeyboardEvent(
eventPropName: string,
event: ReactDOMResponderEvent,
context: ReactDOMResponderContext,
type: KeyboardEventType,
target: Element | Document,
): void {
// ⚛️创建合成事件对象,在这个函数中会规范化事件的key属性
const syntheticEvent = createKeyboardEvent(event, context, type, target);
// ⚛️通过Responder上下文分发事件
context.dispatchEvent(eventPropName, syntheticEvent, DiscreteEvent);
}
Экспорт ответчика:
// ⚛️createResponder把keyboardResponderImpl转换为组件形式
export const KeyboardResponder = React.unstable_createResponder(
'Keyboard',
keyboardResponderImpl,
);
// ⚛️创建hooks形式
export function useKeyboardListener(props: KeyboardListenerProps): void {
React.unstable_useListener(KeyboardResponder, props);
}
Читатели должны сейчасОтветственность ответчикаС некоторым базовым пониманием, он в основном делает следующие вещи:
- Объявите собственные события (например, 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!
над!