Механизм событий React — обзор исходного кода (включено)

внешний интерфейс исходный код браузер

однажды спросили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Он переработан и имеет кроссбраузерные возможности, так же и способ монтирования отличается от метода события родного браузера, т.к. событие в это время привязывается кdocumenton, поэтому вызывайте некоторые методы событий, такие как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 — обзор исходного кода (ниже)

Категории