однажды спросилиReact
Вопрос механизма событий, я действительно мало что знаю об этом, потому что большая часть работы обычно используетVue
,дляReact
Уровень знакомства ограничивается умением им пользоваться, я не изучал конкретную логику реализации, но не могу просто сказать, что не знаю ее, к счастью, я ее понимаю.Vue
механизм событий, поэтомуVue
Механизм событий,React
должен иVue
почти"
Позже я подумал, что это не должно быть так просто, поэтому я поискал соответствующие статьи в Интернете и обнаружил, что это слишком просто для меня, чтобы думать.Vue
Компилируя шаблоны, анализируя директивы событий, присоединяя события и обратные вызовы событий кvnode tree
на, вpatch
Фаза создания и обновления процесса будетvnode tree
процесс, получить каждыйvnode
Имея дополнительную информацию о событии, вы можете позвонить родномуDOM API
Процесс регистрации или удаления соответствующего события все еще относительно ясен, иReact
Это отдельная реализация набора механизмов событий
Эта статья начинается с
React v16.5.2
анализ исходного кода
Основной процесс
существуетreact
исходный кодreact-dom/src/events/ReactBrowserEventEmitter.js
В начале файла есть такой большой комментарий:
/**
* Summary of `ReactBrowserEventEmitter` event handling:
*
* - Top-level delegation is used to ......
* ......
*
* +------------+ .
* | DOM | .
* +------------+ .
* | .
* v .
* +------------+ .
* | ReactEvent | .
* | Listener | .
* +------------+ . +-----------+
* | . +--------+|SimpleEvent|
* | . | |Plugin |
* +-----|------+ . v +-----------+
* | | | . +--------------+ +------------+
* | +-----------.--->|EventPluginHub| | Event |
* | | . | | +-----------+ | Propagators|
* | ReactEvent | . | | |TapEvent | |------------|
* | Emitter | . | |<---+|Plugin | |other plugin|
* | | . | | +-----------+ | utilities |
* | +-----------.--->| | +------------+
* | | | . +--------------+
* +-----|------+ . ^ +-----------+
* | . | |Enter/Leave|
* + . +-------+|Plugin |
* +-------------+ . +-----------+
* | application | .
* |-------------| .
* | | .
* | | .
* +-------------+ .
* .
* React Core . General Purpose Event Plugin System
*/
Я опустил первый абзац текста этого комментария, который в основном описываетReact
Механизм событий, то есть некоторые действия, которые должен выполнять код в этом файле, вероятно, означает, что делегирование событий является очень часто используемой стратегией оптимизации событий браузера, поэтомуReact
Он взял на себя это дело, а также тщательно устранил различия между браузерами, предоставив разработчикам опыт кроссбраузерной разработки, в основном с использованиемEventPluginHub
Эта штука отвечает за планирование хранения событий, синтез событий, их создание и уничтожение в виде пулов объектов.Что касается следующей структурной схемы, то это графическое описание механизма событий.
Согласно этому комментарию, можно выделить следующие моменты:
-
React
Событие использует механизм доверительного управления событиями, роль общего события возложена на сокращение количества регистрационных событий, снижение накладных расходов на память, оптимизацию производительности браузера,React
Это тоже сделано для такой цели.Кроме того,это еще и для лучшего управления событиями.На самом деле,React
Все события в конечном итоге делегируютсяdocument
этот топDOM
начальство - Поскольку все события делегируются
document
, тогда должен быть механизм управления, все события инициируются и вызываются в очереди в порядке поступления - Поскольку они уже захватили событие, было бы расточительством не сделать что-то еще с событием, так что
React
имеет собственное синтетическое событие (SyntheticEvent
), синтетическое событие состоит из соответствующегоEventPlugin
Отвечая за синтез, разные типы событий состоят из разныхplugin
синтетический, напр.SimpleEvent Plugin
,TapEvent Plugin
Ждать - Чтобы еще больше повысить производительность событий, используйте
EventPluginHub
Эта штука отвечает за создание и уничтожение синтетических событийных объектов.
В качестве примера для анализа используется следующий код:
export default class MyBox extends React.Component {
clickHandler(e) {
console.log('click callback', e)
}
render() {
return (
<div className="box" onClick={this.clickHandler}>文本内容</div>
)
}
}
регистрация на мероприятие
Посмотрите только на соответствующий основной процесс, другие, такие какvnode
Предварительные процессы, такие как созданиеsetInitialDOMProperties
Этот метод начинает искать, этот метод в основном используется для обходаReactNode
изprops
объект, который дает реальность, которая в конечном итоге будет визуализированаDOM
Объекты устанавливают ряд свойств, таких какstyle
,class
,autoFocus
, также включаютinnerHTML
,event
Обработка, например.box
элементальprops
Структура объекта следующая:
В этом методе естьcase
, предназначенный для обработки событий:
// react-dom/src/client/ReactDOMComponent.js
else if (registrationNameModules.hasOwnProperty(propKey)) {
if (nextProp != null) {
if (true && typeof nextProp !== 'function') {
warnForInvalidEventListener(propKey, nextProp);
}
// 处理事件类型的 props
ensureListeningTo(rootContainerElement, propKey);
}
}
один из нихregistrationNameModules
В этой переменной много свойств, и все они связаны сReact
События, связанные с:
в примереonClick
этоprops
очевидно совпадает, поэтому можно выполнитьensureListeningTo
Сюда:
// react-dom/src/client/ReactDOMComponent.js
function ensureListeningTo(rootContainerElement, registrationName) {
var isDocumentOrFragment = rootContainerElement.nodeType === DOCUMENT_NODE || rootContainerElement.nodeType === DOCUMENT_FRAGMENT_NODE;
var doc = isDocumentOrFragment ? rootContainerElement : rootContainerElement.ownerDocument;
listenTo(registrationName, doc);
}
В этом методе первыйrootContainerElement
этоdocument
илиFragment
(узел фрагмента документа), в примере передается.box
этоdiv
, очевидно, нет, поэтомуdoc
Этой переменной присваивается значениеrootContainerElement.ownerDocument
, эта штука на самом деле.box
Существующийdocument
элемент, поместите этоdocument
к следующемуlistenTo
внутри,делегация мероприятияЭто то, что делается здесь, все события в конечном итоге будут делегированыdocument
илиfragment
вверх, большую часть времениdocument
, то этоregistrationName
это название событияonClick
Затем приступайте к выполнениюlistenTo
метод, этот метод фактически является входом зарегистрированного события, в методе есть такое предложение:
// react-dom/src/events/ReactBrowserEventEmitter.js
var dependencies = registrationNameDependencies[registrationName];
registrationName
только что прошелonClick
, а переменнаяregistrationNameDependencies
хранитсяReact
Имя события, соответствующее собственному имени события браузера.Map
, сквозь этоmap
Получите соответствующее собственное имя события браузера,registrationNameDependencies
Структура выглядит следующим образом:
можно увидеть,React
Он делает некоторые вещи, совместимые с разными браузерами, для имени события, например, передаетonChange
событие, оно будет автоматически соответствоватьblur change click focus
и другие нативные события браузера
Затем повторите этоdependencies
массив, перейдите к следующемуcase
:
// react-dom/src/events/ReactBrowserEventEmitter.js
switch (dependency) {
// 省略一些代码
default:
// By default, listen on the top level to all non-media events.
// Media events don't bubble so adding the listener wouldn't do anything.
var isMediaEvent = mediaEventTypes.indexOf(dependency) !== -1;
if (!isMediaEvent) {
trapBubbledEvent(dependency, mountAt);
}
break;
}
Кромеscroll focus blur cancel close
путьtrapCapturedEvent
метод,invalid submit reset
За исключением того, что метод не обрабатывается, все остальные типы событий исчезли.default
,воплощать в жизньtrapBubbledEvent
Сюда,trapCapturedEvent
а такжеtrapBubbledEvent
Единственная разница между ними состоит в том, что для последнего синтетического события первый регистрируетэтап захватапрослушиватели событий, которые регистрируюткипящая стадияпрослушиватель событий для
Поскольку большинство синтетических прокси-серверов событий регистрируются вкипящая стадияПрослушиватель событий, делегированный
document
регистрируется в прослушивателе событий фазы всплытия, поэтому даже если вы явно объявляете фазу захватаReact
такие события, какonClickCapture
, ответ этого события также будет позже, чем событие захвата собственного события ивсплывающее событиеНа самом деле, все собственные ответы на события (будь то всплывающие или захватывающие) будут раньше, чемReact
синтетическое событие (SyntheticEvent
), вызываемый на родном событииe.stopPropagation()
предотвратит соответствующееSyntheticEvent
, потому что соответствующее событие вообще не может быть достигнутоdocument
Этот уровень делегирования событий заблокирован
Между ними нет большой разницы,trapBubbledEvent
Этот наиболее часто используемый пример также будет выполнять этот метод, поэтому просто следуйте этому методу, чтобы увидеть:
// react-dom/src/events/EventListener.js
// 对于本示例来说,topLevelType就是 click,element就是 document
function trapBubbledEvent(topLevelType, element) {
if (!element) {
return null;
}
var dispatch = isInteractiveTopLevelEventType(topLevelType) ? dispatchInteractiveEvent : dispatchEvent;
addEventBubbleListener(element, getRawEventName(topLevelType),
// Check if interactive and wrap in interactiveUpdates
dispatch.bind(null, topLevelType));
}
addEventBubbleListener
Этот метод принимает три параметра, в этом примере первый параметрelement
На самом деле этоdocument
элемент,getRawEventName(topLevelType)
то естьclick
событие, третий параметрdispatch
то естьdispatchInteractiveEvent
,dispatchInteractiveEvent
На самом деле, в конце концовdispatchEvent
Этот метод просто делает некоторые дополнительные действия перед выполнением этого метода. Вам не нужно заботиться об этом здесь. Вы можете думать, что на данный момент это одно и то же.
посмотриaddEventBubbleListener
Сюда:
// react-dom/src/events/EventListener.js
export function addEventBubbleListener(
element: Document | Element,
eventType: string,
listener: Function,
): void {
element.addEventListener(eventType, listener, false);
}
Этот метод очень прост, просто используйтеaddEventListener
Датьdocument
зарегистрировал всплывающее событие,listener
Обратный вызов этого события является ранее переданным вdispatch.bind(null, topLevelType)
Блок-схема выглядит следующим образом:
распределение событий
Поскольку все события делегируются и регистрируются наdocument
Выше при срабатывании события обязательно нужен процесс распределения событий, чтобы выяснить, какой элемент спровоцировал событие и выполнить соответствующую callback-функцию.Следует отметить, что поскольку сам элемент не регистрирует никаких событий, к нему прибывают делегатыdocument
, поэтому событие, которое будет запущено,React
Встроенные синтетические события, а не собственные события браузера, но в целом требуют процесса распространения.
Как упоминалось в предыдущей регистрации на мероприятие, зарегистрируйтесь наdocument
По событию будет запущена соответствующая функция обратного вызова.dispatchEvent
Этот метод, введите этот метод:
// react-dom/src/events/ReactDOMEventListener.js
const nativeEventTarget = getEventTarget(nativeEvent);
let targetInst = getClosestInstanceFromNode(nativeEventTarget);
Сначала найдите триггер событияDOM
а такжеReact Component
, найти правдуDOM
Легче найти, напрямую принять обратный вызов событияevent
параметрическийtarget | srcElement | window
Хорошо, тогда этоnativeEventTarget
Объект подвешен на__reactInternalInstance
атрибут в начале, этот атрибутinternalInstanceKey
, значение которого является текущимReact
соответствующий экземпляруReact Component
Затем продолжайте смотреть вниз:
try {
// Event queue being processed in the same cycle allows
// `preventDefault`.
batchedUpdates(handleTopLevel, bookKeeping);
} finally {
releaseTopLevelCallbackBookKeeping(bookKeeping);
}
batchedUpdates
, буквально означает пакетное обновление, здесь фактически помещается текущее инициированное событие в пакетную очередь, среди которыхhandleTopLevel
Это ядро распределения событий:
// react-dom/src/events/ReactDOMEventListener.js
let targetInst = bookKeeping.targetInst;
// Loop through the hierarchy, in case there's any nested components.
// It's important that we build the array of ancestors before calling any
// event handlers, because event handlers can modify the DOM, leading to
// inconsistencies with ReactMount's node cache. See #1105.
let ancestor = targetInst;
do {
if (!ancestor) {
bookKeeping.ancestors.push(ancestor);
break;
}
const root = findRootContainerNode(ancestor);
if (!root) {
break;
}
bookKeeping.ancestors.push(ancestor);
ancestor = getClosestInstanceFromNode(root);
} while (ancestor);
Тут я сначала подумал, что нужно пройтись по всем родительским узлам от текущего узла вверх, а потом сохранить ссылку этого узла, но позже выяснил, что это не так
function findRootContainerNode(inst) {
// TODO: It may be a good idea to cache this to prevent unnecessary DOM
// traversal, but caching is difficult to do correctly without using a
// mutation observer to listen for all DOM changes.
while (inst.return) {
inst = inst.return;
}
if (inst.tag !== HostRoot) {
// This can happen if we're in a detached tree.
return null;
}
return inst.stateNode.containerInfo;
}
findRootContainerNode
Это поиск корневого узла вдоль родительского элемента, что обычно и бывает.<div id="app"></div>
этого узла
Как ты это нашел? согласно каждомуReact Node
Вверхreturn
свойства, каждыйReact Node
там будетreturn
собственность, этоreturn
Атрибут указывает на родительский узел текущего узла.Корневой узел не имеет родительского узла, поэтому нетreturn
останавливаться, когда корневой узел найден, и возвращать корневой узел.
В общем, для того жеReact
С точки зрения приложения, этот корневой узел должен быть зафиксирован, чтобы его можно было кэшировать, и нет необходимости каждый раз перемещаться вверх, чтобы найти его, но функция перемещения вверх, чтобы найти корневой узел, здесь заключается не только в том, чтобы просто найти корневой узел и определитьfindRootContainerNode
Всегда ли этот метод может найти корневой узел из текущего узла? Если корневой узел не может быть найден из текущего узла, это означает, что с этим узлом возникла проблема. Например, этот узел былReact Tree
удален, то с этим узлом в дальнейшем ничего делать не надо.
Эта причина тоже была четко написана в комментариях, т.е.#1105этоissue
решенная проблема
зачем проходитьdo...while
много раз искатьroot
, на самом деле, это еще и для решения особых ситуаций.В общем, каждое приложение имеет только одинReact
например, если да, то здесь не требуетсяdo...while
Циклический, но в некоторых случаях более одногоReact
заявление
function getParent(inst) {
do {
inst = inst.return;
// TODO: If this is a HostRoot we might want to bail out.
// That is depending on if we want nested subtrees (layers) to bubble
// events to their parent. We could also go through parentNode on the
// host node but that wouldn't work for React Native and doesn't let us
// do the portal feature.
} while (inst && inst.tag !== HostComponent);
if (inst) {
return inst;
}
return null;
}
Например, я вReact
В примере приложения дополнительныйReact
экземпляр приложения, затем внутренний слойReact
Событие добавляется к узлу в приложении. Когда событие выполняется, например всплывающее событие, событие обязательно начнется с текущего узла и пройдет через родительский узел к корневому узлу.HostComponent
, поэтому он идет к корневому узлу, то есть к внутреннему корневому узлу, но теоретически он должен пройти весь путь к внешнему корневому узлу, поэтому внутренний корневой узел снова используется в качестве источника триггера события, продолжайте поиск , соедините верхний наружный слойReact
приложение, поднимающееся к внешнему корневому узлу
Продолжить вниз:
// react-dom/src/events/ReactDOMEventListener.js
for (let i = 0; i < bookKeeping.ancestors.length; i++) {
targetInst = bookKeeping.ancestors[i];
runExtractedEventsInBatch(
bookKeeping.topLevelType,
targetInst,
bookKeeping.nativeEvent,
getEventTarget(bookKeeping.nativeEvent),
);
}
runExtractedEventsInBatch
Метод фактически является точкой входа для выполнения события.
выполнение события
runExtractedEventsInBatch
Этот метод вызывает еще два метода:extractEvents
,runEventsInBatch
,extractEvents
Используется для построения событий синтеза,runEventsInBatch
для пакетной обработкиextractEvents
Сконструированное синтетическое событие
Создание синтетических событий
найти правильное синтетическое событиеplugin
Первый взглядextractEvents
// packages/events/EventPluginHub.js
let events = null;
for (let i = 0; i < plugins.length; i++) {
// Not every plugin in the ordering may be loaded at runtime.
const possiblePlugin: PluginModule<AnyNativeEvent> = plugins[i];
if (possiblePlugin) {
const extractedEvents = possiblePlugin.extractEvents(
topLevelType,
targetInst,
nativeEvent,
nativeEventTarget,
);
if (extractedEvents) {
events = accumulateInto(events, extractedEvents);
}
}
}
первый ходplugins
,этоplugins
Синтез всех событийplugins
массив коллекций, всего5
Добрый(v15.x
версия7
виды), этиplugins
расположены вreact-dom/src/events
В этой папке он существует в виде отдельного файла, имя файлаEventPlugin
В конце концов, ониEventPluginHub
Введено в фазу инициализации:
// react-dom/src/client/ReactDOMClientInjection.js
EventPluginHub.injection.injectEventPluginsByName({
SimpleEventPlugin: SimpleEventPlugin,
EnterLeaveEventPlugin: EnterLeaveEventPlugin,
ChangeEventPlugin: ChangeEventPlugin,
SelectEventPlugin: SelectEventPlugin,
BeforeInputEventPlugin: BeforeInputEventPlugin,
});
extractEvents
метод использовалfor
петля, положить всеplugin
Все они выполнены один раз, и для личного понимания не обязательно.Найдите нужное.plugin
После выполнения вы можете сразуbreak
Потерянный
Например, для этого примераclick
событие, подходящееplugin
даSimpleEventPlugin
,другиеplugin
Даже если вы войдете и пройдете через него один раз, это просто бесполезное усилие, потому что после выполнения другихplugin
получено послеextractedEvents
не удовлетвореныif (extractedEvents)
Это условие не может быть даноevents
Это назначение переменной или переопределение назначения, конечно, этот код может также иметь другие относительно секретные функции.
possiblePlugin.extractEvents
Это предложение должно вызвать соответствующееplugin
метод построения синтетических событий, др.plugin
Чтобы не расширять анализ, для этого примераSimpleEventPlugin
, посмотри на егоextractEvents
:
// react-dom/src/events/SimpleEventPlugin.js
const dispatchConfig = topLevelEventsToDispatchConfig[topLevelType];
if (!dispatchConfig) {
return null;
}
Во-первых, посмотрите наtopLevelEventsToDispatchConfig
Есть ли у этого объектаtopLevelType
Это свойство, пока оно существует, указывает, что текущее событие может быть использованоSimpleEventPlugin
Создайте синтетическое событие, для этого примера,topLevelType
то естьclick
,а такжеtopLevelEventsToDispatchConfig
Структура выглядит следующим образом:
Эти свойства являются некоторыми общими именами событий, очевидноclick
даtopLevelEventsToDispatchConfig
Имя атрибута , которое соответствует условиям и может продолжать выполняться, за которым следуетswitch...case
Заявление о суждении для этого примера будет в следующемcase
кудаbreak
Терять:
// react-dom/src/events/SimpleEventPlugin.js
case TOP_CLICK:
// 省略了一些代码
EventConstructor = SyntheticMouseEvent;
break;
SyntheticMouseEvent
можно рассматривать какSimpleEventPlugin
конкретный ребенок изplugin
, что эквивалентноSimpleEventPlugin
это большое понятиеplugin
подразделяется на другой слой, за исключениемSyntheticMouseEvent
в дополнение кSyntheticWheelEvent
,SyntheticClipboardEvent
,SyntheticTouchEvent
Ждать
Извлечь объект из синтетического пула объектов событий
установить конкретныйEventConstructor
После этого продолжайте выполнять:
// react-dom/src/events/SimpleEventPlugin.js
const event = EventConstructor.getPooled(
dispatchConfig,
targetInst,
nativeEvent,
nativeEventTarget,
);
accumulateTwoPhaseDispatches(event);
return event;
getPooled
изevent
Синтетическое событие извлекается из пула объектов.React
Одним из основных моментов является то, что все события кэшируются в пуле объектов, что позволяет значительно сократить время создания и уничтожения объектов и повысить производительность.
getPooled
даEventConstructor
метод наEventConstructor
Он зависает при инициализации, но в конечном счете этот метод находится вSyntheticEvent
На этом объекте блок-схема выглядит следующим образом:
этоgetPooled
На самом деле этоgetPooledEvent
,существуетSyntheticEvent
Начальное значение устанавливается в процессе инициализации:
// packages/events/SyntheticEvent.js
addEventPoolingTo(SyntheticEvent);
// 省略部分代码
function addEventPoolingTo(EventConstructor) {
EventConstructor.eventPool = [];
EventConstructor.getPooled = getPooledEvent;
EventConstructor.release = releasePooledEvent;
}
Тогда взглянитеgetPooledEvent
:
// packages/events/SyntheticEvent.js
function getPooledEvent(dispatchConfig, targetInst, nativeEvent, nativeInst) {
const EventConstructor = this;
if (EventConstructor.eventPool.length) {
const instance = EventConstructor.eventPool.pop();
EventConstructor.call(
instance,
dispatchConfig,
targetInst,
nativeEvent,
nativeInst,
);
return instance;
}
return new EventConstructor(
dispatchConfig,
targetInst,
nativeEvent,
nativeInst,
);
}
Когда событие запускается в первый раз (в этом примере этоclick
мероприятие),EventConstructor.eventPool.length
для0
, так как это первый раз, когда событие запускается, в пуле объектов нет соответствующей искусственной ссылки на событие, поэтому его необходимо инициализировать, а когда событие запускается позже, нет необходимостиnew
Вместо этого следуйте приведенной выше логике, возьмите его непосредственно из пула объектов и передайтеEventConstructor.eventPool.pop();
Получить экземпляр синтетического объекта
Здесь сначала посмотрите на процесс инициализации, который будет выполнятьсяnew EventConstructor
В этом предложении, сказанном ранее, эту вещь можно рассматривать какSyntheticEvent
подкласс , илиSyntheticEvent
Что расширяется, как расширяется?extend
метод:
const SyntheticMouseEvent = SyntheticUIEvent.extend({
screenX: null,
screenY: null,
clientX: null,
clientY: null,
pageX: null,
pageY: null,
// 省略部分代码
})
первый,SyntheticMouseEvent
Это синтетическое событие имеет свои собственные свойства, которые на самом деле такие же, как и собственный объект параметра обратного вызова события браузера.event
По свойствам особой разницы нет.event
Сказать,SyntheticMouseEvent
Или синтетические событияSyntheticEvent
Свойства даныReact
активно генерируется черезReact
, чтобы прикрепленный к нему атрибут описания полностью соответствовалW3C
стандарт, поэтому он имеет кросс-браузерную совместимость на уровне событий, имеет тот же интерфейс, что и собственные события браузера, и имеетstopPropagation()
а такжеpreventDefault()
и т.д. метод
Для метода обратного вызова события щелчка в этом примере:
clickHandler(e) {
console.log('click callback', e)
}
один из нихe
На самом деле это синтетическое событие, а не родное событие браузера.event
, поэтому разработчикам не нужно учитывать совместимость браузера, просто следуйтеw3c
Значение спецификации достаточно.Если вам нужно получить доступ к собственному объекту события, вы можете передатьe.nativeEvent
получать
SyntheticUIEvent
Это дело в основном дляSyntheticMouseEvent
Добавьте несколько дополнительных атрибутов, здесь все равно, тогда этоSyntheticMouseEvent.extend
снова поSyntheticEvent
расширение (extend
), так что в конечном итогеnew SyntheticEvent
Первый взглядextend
метод:
// packages/events/SyntheticEvent.js
SyntheticEvent.extend = function(Interface) {
const Super = this;
// 原型式继承
const E = function() {};
E.prototype = Super.prototype;
const prototype = new E();
// 构造函数继承
function Class() {
return Super.apply(this, arguments);
}
Object.assign(prototype, Class.prototype);
Class.prototype = prototype;
Class.prototype.constructor = Class;
Class.Interface = Object.assign({}, Super.Interface, Interface);
Class.extend = Super.extend;
addEventPoolingTo(Class);
return Class;
};
А вот и классикаПаразитическая композиционная наследственность, этот паразитный метод является наиболее зрелым, большинство библиотек используют этот метод наследования,React
Он также используется здесь, пустьEventConstructor
унаследовано отSyntheticEvent
,получатьSyntheticEvent
Некоторые свойства и методы наeventPool
,getPooled
Ждать
Так как существуют отношения наследования, тоnew EventConstructor
Этот подкласс, естественно, вызовет родительский классSyntheticEvent
изnew
метод, то есть начиная вызывать конструктор синтетического компонента, и начиная конструировать синтетическое событие, главное смонтировать параметры на родном событии браузера в синтетическое событие, в том числеclientX
,screenY
,timeStamp
и другие свойства события,preventDefault
,stopPropagation
и другие методы событий, такие как вышеупомянутыйe.nativeEvent
В это время монтируется полученное нативное событие:
// packages/events/SyntheticEvent.js
this.nativeEvent = nativeEvent;
Все смонтированные свойства передаются черезReact
Он переработан и имеет кроссбраузерные возможности, так же и способ монтирования отличается от метода события родного браузера, т.к. событие в это время привязывается кdocument
on, поэтому вызывайте некоторые методы событий, такие какe.stopPropagation()
На самом деле дляdocument
Элемент, вызываемый элементом, не совпадает с исходным ожидаемым элементом, поэтому для того, чтобы производительность синтетического события достигла эффекта собственного события, требуется дополнительная обработка этих методов.
Метод обработки также относительно прост, то есть добавление флагового бита, например, дляstopPropagation
Сказать,React
Он упакован:
// packages/events/SyntheticEvent.js
stopPropagation: function() {
const event = this.nativeEvent;
if (!event) {
return;
}
if (event.stopPropagation) {
event.stopPropagation();
} else if (typeof event.cancelBubble !== 'unknown') {
// The ChangeEventPlugin registers a "propertychange" event for
// IE. This event does not support bubbling or cancelling, and
// any references to cancelBubble throw "Member not found". A
// typeof check of "unknown" circumvents this issue (and is also
// IE specific).
event.cancelBubble = true;
}
this.isPropagationStopped = functionThatReturnsTrue;
}
Первый — получить родное событие браузера, а затем вызвать соответствующийstopPropagation
метод, вам нужно обратить внимание здесь, здесьevent
Кdocument
Запускается объект параметра обратного вызова события, сгенерированный событием для этого элемента, а не объект параметра обратного вызова события фактического элемента.document
Инициированные события, такие как события щелчка, называютсяe.stopPropagation
, предотвращая продолжение событияdocument
илиFragment
родительское размножение
// packages/events/SyntheticEvent.js
// 这个函数其实就是返回了一个 true,与此对应的,还有个函数名为 functionThatReturnsFalse的函数,用来返回 false
function functionThatReturnsTrue() {
return true;
}
Ключthis.isPropagationStopped = functionThatReturnsTrue;
Это предложение эквивалентно установке бита флага.Для пузырькового события, когда событие запускается, оно перемещается от дочернего элемента к родительскому элементу, и обратный вызов события, соответствующий каждому слою элементов, будет выполняться последовательно, но если текущий элемент соответствует синтетическому событиюisPropagationStopped
дляtrue
значение, цикл обхода будет прерван, то есть обход не продолжится, синтетические события всех родительских элементов текущего элемента не сработают, а финальный эффект будет вызван нативным событием браузера.e.stopPropagation()
эффект такой же
Принцип захвата событий тот же, за исключением того, что он проходит от родителя к дочернему.
Эти методы событий (включаяstopPropagation
,preventDefault
и т. д.) обычно вызываются в функции обратного вызова события, а функция обратного вызова события выполняется в последующих пакетных операциях.
var event = EventConstructor.getPooled(dispatchConfig, targetInst, nativeEvent, nativeEventTarget);
accumulateTwoPhaseDispatches(event);
Получить все экземпляры элементов и функции обратного вызова событий, связанные с текущим событием триггера.
Целая куча вышеизложенного из первого предложения приведенного выше кодаgetPooled
Заход на вход в основном для получения синтетического события.После получения базового синтетического события начните дальнейшую обработку синтетического события, то естьaccumulateTwoPhaseDispatches
Что этот метод должен делать, этот метод включает в себя множество процессов, поэтому нарисуйте картинку, чтобы было понятнее:
Код и метод вызова относительно тривиальны, но цель очень ясна, то есть сохранить все функции обратного вызова событий, висящие на текущем элементе и его родительских элементах, включая захват событий (captured
) и всплывающие события (bubbled
), сохранить в событиеevent
из_dispatchListeners
атрибут, а текущий элемент и его родительский элементreact
экземпляр (вv16.x
версия, экземпляр здесьFiberNode
)Сохранитьevent
из_dispatchInstances
атрибут
После получения всех экземпляров элементов, связанных с событиями, и функций обратного вызова событий можно выполнять пакетную обработку синтетических событий.
из-за
React
Механизм события более сложен, а сказать есть о чем, поэтому он разбит на две статьи, остальной анализ можно найти в статье.Механизм событий React — обзор исходного кода (ниже)