Реагируйте на синтетические события от мелких до глубоких

React.js

предисловие

📢 Статья впервые опубликована в блоге:Блог Ах Куана

💕 Напоминание: Ниже приведено чтение исходного кода синтетических событий React, полный текст немного длинноват, но! Если вы действительно хотите знать этоСекрет за кадром, ты должен быть терпеливым!

Недавно я выполнял функцию, а затем不小心вышел наРеагировать на синтетическое событиеЯма, движимая любопытством, пошла посмотретьСинтетическое событие официального сайта ReactОбъяснение, я не знаю, если я его не вижу, я вздрагиваю, когда вижу его...

SyntheticEventЧто это за фигня? Почему это вышлопул событий?

У меня простая функция спроса, почему я могу вытаскивать этих призраков? ?

Давайте кратко взглянем на то, что моя требуемая функция ???

предохранитель

нужно сделать всплывающее окно打开/关闭функция, при нажатииbuttonКогда он откроется, щелкните всплывающее окно, когда оно откроется.区域снаружи, он должен быть закрыт.

Это просто, прямоbuttonзарегистрировать событие клика наdocument.bodyЗарегистрируйтесь для события клика, затем в弹窗containerТрудно перестать булькать?

class FuckEvent extends React.PureComponent {
  state = {
    showBox: false
  }
  componentDidMount() {
    document.body.addEventListener('click', this.handleClickBody, false)
  }
  componentWillUnmount() {
    document.body.removeEventListener('click', this.handleClickBody, false)
  }
  handleClickBody = () => {
    this.setState({
      showBox: false
    })
  }
  handleClickButton = () => {
    this.setState({
      showBox: true
    })
  }

  render() {
    return (
      <div>
        <button onClick={this.handleClickButton}>点击我显示弹窗</button>

        {this.state.showBox && (
          <div onClick={e => e.stopPropagation()}>我是弹窗</div>
        )}
      </div>
    )
  }
}

Это очень просто, я очень рад щелкнуть область всплывающего окна. …

тогда...меня нет...щелчок по области всплывающего окна также закрывает всплывающее окно. . . какого хрена??????

С этим вопросом я пошел不归之路...

делегация мероприятия

Все мы знаем, что такое делегирование событий (если не знаете, выходя, поверните налево 👈).爸爸

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

Видите ли, делегирование событий — это так хорошо, вы думаете, что React не будет его использовать? О, React не только используется, но и очень скользкий~

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

React реализуетСлой синтетических событий, именно этот уровень событий устраняет проблему совместимости между стандартами IE и W3C.

📌 Итак, вопрос, что такое синтетические события и нативные события????

  • Родное событие: вcomponentDidMount生命周期внутриaddEventListenerсвязанное событие

  • Синтетические события: события, связанные с JSX, такие какonClick={() => this.handle()}

Помните пример выше? Мы привязываем событие к элементу DOM всплывающего окна, чтобы предотвратить всплывание

{
  this.state.showBox && <div onClick={e => e.stopPropagation()}>我是弹窗</div>
}

затем вcomponentDidMount生命周期Внутри тело обязано щелкнуть

componentDidMount() {
  document.body.addEventListener('click', this.handleClickBody, false)
}

componentWillUnmount() {
  document.body.removeEventListener('click', this.handleClickBody, false)
}

Давайте проанализируем,Поскольку инициирование синтетических событий основано на механизме событий браузера, они направляются к элементу верхнего уровня через механизм всплытия, а затем обрабатываются с помощью dispatchEvent.

Просмотрите механизм событий браузера

Над документом находится окно, а вот картинка из книги "Advanced JavaScript Programming"

Выполнение событий браузера должно пройти три этапа: этап захвата — этап целевого элемента — этап всплытия.

🙋 Вопрос: Если в это время синтетическое событие будет заблокировано, будет ли выполнено нативное событие? Ответ: да!

📢 Ответ: поскольку собственное событие выполняется до синтетического события (личное понимание: зарегистрированное собственное событие было выполнено, а синтетическое событие находится на целевом этапе, всплывающее сообщение, которое оно предотвращает, предназначено только для предотвращения всплытия синтетического события, но нативное событие уже находится в стадии захвата. выполнено)

Синтетические функции событий

React сам реализовал такой набор механизмов событий, усовершенствовал его на основе системы событий DOM, уменьшил потребление памяти и в наибольшей степени решил проблему несовместимости таких браузеров, как IE.

Так каковы его характеристики?

  • События, зарегистрированные в React, в конечном итоге привязаны кdocumentВ этом DOM, а не в DOM, соответствующем компоненту React (уменьшение накладных расходов на память связано с тем, что все события привязаны к документу, а другие узлы не являются связанными событиями)

  • Сам React реализует механизм всплытия событий, поэтому мыevent.stopPropagation()Неверная причина.

  • React выполняет возврат от запущенного компонента к родительскому компоненту в виде очереди, а затем вызывает их обратный вызов, определенный в JSX.

  • React имеет собственный набор синтетических событий.SyntheticEvent, не родной, можно самому зайти на официальный сайт

  • Реагирование управляет созданием и разрушением объектов синтетического события в виде пулов объекта, уменьшая выделение мусора и нового объекта распределения памяти и повышения производительности

Реагировать на систему событий

Увидев это, вы должны иметь простое представление о синтетических событиях React.Давайте взглянем на исходный код~

👉Исходный код ReactBrowserEventEmitter

мы вReactBrowserEventEmitter.jsКак вы можете видеть в файле, диаграмма фреймворка системы композиции React

/**
 * React和事件系统概述:
 *
 * +------------+    .
 * |    DOM     |    .
 * +------------+    .
 *       |           .
 *       v           .
 * +------------+    .
 * | ReactEvent |    .
 * |  Listener  |    .
 * +------------+    .                         +-----------+
 *       |           .               +--------+|SimpleEvent|
 *       |           .               |         |Plugin     |
 * +-----|------+    .               v         +-----------+
 * |     |      |    .    +--------------+                    +------------+
 * |     +-----------.--->|EventPluginHub|                    |    Event   |
 * |            |    .    |              |     +-----------+  | Propagators|
 * | ReactEvent |    .    |              |     |TapEvent   |  |------------|
 * |  Emitter   |    .    |              |<---+|Plugin     |  |other plugin|
 * |            |    .    |              |     +-----------+  |  utilities |
 * |     +-----------.--->|              |                    +------------+
 * |     |      |    .    +--------------+
 * +-----|------+    .                ^        +-----------+
 *       |           .                |        |Enter/Leave|
 *       +           .                +-------+|Plugin     |
 * +-------------+   .                         +-----------+
 * | application |   .
 * |-------------|   .
 * |             |   .
 * |             |   .
 * +-------------+   .
 *                   .
 */

В исходном коде есть большая серия английских пояснений.Я помог вам с переводом Google.Вкратце, это:

  • Делегирование верхнего уровня используется для захвата самых примитивных событий браузера. Оно в основном отвечает за ReactEventListener. После внедрения ReactEventListener он может поддерживать подключаемые источники событий. Этот процесс происходит в основном потоке.

  • React нормализует и дедуплицирует события, чтобы обойти причуды браузера. Это можно сделать в рабочем потоке.

  • Перенаправьте эти локальные события (со связанным типом верхнего уровня, чтобы их перехватить) вEventPluginHub, который спросит плагин, хочет ли он извлечь какие-либо синтетические события.

  • Затем EventPluginHub будет обрабатывать каждое событие, аннотируя его «отправлениями» (последовательностями слушателей и идентификаторов, которые заботятся об этом событии).

  • Затем EventPluginHub отправит событие отправки.

❗ Рекомендуется сразу перейти к англоязычной аннотации, перевод может быть не очень стандартным.

Глядя на диаграмму фреймов выше, мы должны сначала узнать, что это такое, и мы также можем узнать это, посмотрев на имя:

  • ReactEventListener: отвечает за регистрацию событий.
  • ReactEventEmitter: отвечает за распространение событий.
  • EventPluginHub: отвечает за хранение и распространение событий.
  • Плагин: создавайте различные синтетические события на основе разных типов событий.

👇 Давайте пошагово рассмотрим, как это работает

регистрация на мероприятие

Зарегистрировать событие в React очень просто:

class TaskEvent extends Reac.PureComponent {
  render() {
    return (
      <div
        onClick={() => {
          console.log('我是注册事件')
        }}
      >
        呵呵呵
      </div>
    )
  }
}

хорошо, запишите этот код, как он регистрируется в системе событий React?

enqueuePutListener()

Когда компоненты создают mountComponent и обновляют updateComponent, они вызывают_updateDOMProperties()метод

📢 Напоминаем, исходный код этого фаста - исходный код react 15.6.1, но я нашел соответствующую версию на github, и она оказалась Pages Not Found... Здесь я использую исходный код этой регистрации событие в статье я прочитал информацию объяснил

mountComponent: function(transaction, hostParent, hostContainerInfo, context) {
  // ...
  var props = this._currentElement.props;
  // ...
  this._updateDOMProperties(null, props, transaction);
  // ...
}
_updateDOMProperties: function (lastProps, nextProps, transaction) {
    // ...
    for (propKey in nextProps) {
      var nextProp = nextProps[propKey];
      var lastProp = propKey === STYLE ? this._previousStyleCopy : lastProps != null ? lastProps[propKey] : undefined;
      if (!nextProps.hasOwnProperty(propKey) || nextProp === lastProp || nextProp == null && lastProp == null) {
        continue;
      }
      if (propKey === STYLE) {
        // ...
      } else if (registrationNameModules.hasOwnProperty(propKey)) {
        // 如果是props这个对象直接声明的属性,而不是从原型链中继承而来的,则处理它
        // 对于mountComponent,lastProp为null。updateComponent二者都不为null。unmountComponent则nextProp为null
        if (nextProp) {
          // mountComponent和updateComponent中,enqueuePutListener注册事件
          enqueuePutListener(this, propKey, nextProp, transaction);
        } else if (lastProp) {
          // unmountComponent中,删除注册的listener,防止内存泄漏
          deleteListener(this, propKey);
        }
      }
    }
}

Приведенный выше код ясно говорит вам, чтоenqueuePutListener()метод для регистрации событий, давайте посмотрим, что это такое

function enqueuePutListener(inst, registrationName, listener, transaction) {
  if (transaction instanceof ReactServerRenderingTransaction) {
    return
  }
  var containerInfo = inst._hostContainerInfo
  var isDocumentFragment =
    containerInfo._node && containerInfo._node.nodeType === DOC_FRAGMENT_TYPE
  // 找到document
  var doc = isDocumentFragment
    ? containerInfo._node
    : containerInfo._ownerDocument
  // 注册事件,将事件注册到document上
  listenTo(registrationName, doc)
  // 存储事件,放入事务队列中
  transaction.getReactMountReady().enqueue(putListener, {
    inst: inst,
    registrationName: registrationName,
    listener: listener
  })
}

💢 Вы это видели?enqueuePutListener()Только что сделал две вещи:

  • позвонивlistenToЗарегистрируйте событие в документе (это событие, зарегистрированное в React, упомянутое ранее, в конечном итоге будет привязано кdocumentна этом ДОМе)

  • транзакционный вызовputListenerХранить события (то есть хранить все события в компоненте React единообразно в объекте и кэшировать их, чтобы найти соответствующий метод для выполнения при срабатывании события)

listenTo()

Хоть и сказано не постить код, но! Глядя на исходный код напрямую, действительно просто и понятно, 👉слушать исходный код

📢 Обратите внимание, что версия реакции — это код текущей основной ветки github.

Давайте посмотрим на код

export function listenTo(
  registrationName: string,
  mountAt: Document | Element | Node
): void {
  const listeningSet = getListeningSetForElement(mountAt)
  const dependencies = registrationNameDependencies[registrationName]

  for (let i = 0; i < dependencies.length; i++) {
    const dependency = dependencies[i]
    // 调用该方法进行注册
    listenToTopLevel(dependency, mountAt, listeningSet)
  }
}

RegistrationName — это передаваемое значение onClick, а переменная RegistrationNameDependencies — это карта, в которой хранится имя события React и собственное имя события браузера. С помощью этой карты можно получить соответствующее собственное имя события браузера.

export function listenToTopLevel(
  topLevelType: DOMTopLevelEventType,
  mountAt: Document | Element | Node,
  listeningSet: Set<DOMTopLevelEventType | string>
): void {
  if (!listeningSet.has(topLevelType)) {
    switch (topLevelType) {
      //...
      case TOP_CANCEL:
      case TOP_CLOSE:
        if (isEventSupported(getRawEventName(topLevelType))) {
          trapCapturedEvent(topLevelType, mountAt) // 捕获阶段
        }
        break
      default:
        const isMediaEvent = mediaEventTypes.indexOf(topLevelType) !== -1
        if (!isMediaEvent) {
          trapBubbledEvent(topLevelType, mountAt) // 冒泡阶段
        }
        break
    }
    listeningSet.add(topLevelType)
  }
}

Игнорируя часть исходного кода выше, мы видим, что записью для регистрации событий является метод listenTo.dependenciesциклический вызовlistenToTopLevel()Метод, называемый методомtrapCapturedEventа такжеtrapBubbledEventдля регистрации для захвата и всплытия событий.

trapCapturedEvent и trapBubbledEvent

Ниже толькоtrapCapturedEventанализировать, 👉исходный адрес trapCapturedEvent,Адрес источника trapBubbleEvent

// 捕获阶段
export function trapCapturedEvent(
  topLevelType: DOMTopLevelEventType,
  element: Document | Element | Node
): void {
  trapEventForPluginEventSystem(element, topLevelType, true)
}

// 冒泡阶段
export function trapBubbledEvent(
  topLevelType: DOMTopLevelEventType,
  element: Document | Element | Node
): void {
  trapEventForPluginEventSystem(element, topLevelType, false)
}
function trapEventForPluginEventSystem(
  element: Document | Element | Node,
  topLevelType: DOMTopLevelEventType,
  capture: boolean // 决定捕获还是冒泡阶段
): void {
  let listener
  switch (getEventPriority(topLevelType)) {
  }
  const rawEventName = getRawEventName(topLevelType)
  if (capture) {
    addEventCaptureListener(element, rawEventName, listener)
  } else {
    addEventBubbleListener(element, rawEventName, listener)
  }
}

😝 Здесь мы можем узнать, что событие захвата передается черезaddEventCaptureListener(), а всплывающее событие проходит черезaddEventBubbleListener()

// 捕获
export function addEventCaptureListener(
  element: Document | Element | Node,
  eventType: string,
  listener: Function
): void {
  element.addEventListener(eventType, listener, true)
}

// 冒泡
export function addEventBubbleListener(
  element: Document | Element | Node,
  eventType: string,
  listener: Function
): void {
  element.addEventListener(eventType, listener, false)
}

хранилище событий

помните вышеenqueuePutListener(), помещаем ли мы события в очередь транзакций?

function enqueuePutListener(inst, registrationName, listener, transaction) {
  //...
  // 注册事件,将事件注册到document上
  listenTo(registrationName, doc)
  // 存储事件,放入事务队列中
  transaction.getReactMountReady().enqueue(putListener, {
    inst: inst,
    registrationName: registrationName,
    listener: listener
  })
}

Вот такputListenerЭта штука, мы можем посмотреть на код

putListener: function (inst, registrationName, listener) {
  // 用来标识注册了事件,比如onClick的React对象。key的格式为'.nodeId', 只用知道它可以标示哪个React对象就可以了
  // step1: 得到组件唯一标识
  var key = getDictionaryKey(inst);

  // step2: 得到listenerBank对象中指定事件类型的对象
  var bankForRegistrationName = listenerBank[registrationName] || (listenerBank[registrationName] = {});

  // step3: 将listener事件回调方法存入listenerBank[registrationName][key]中,比如listenerBank['onclick'][nodeId]
  // 所有React组件对象定义的所有React事件都会存储在listenerBank中
  bankForRegistrationName[key] = listener;

  // ...
}

// 拿到组件唯一标识
var getDictionaryKey = function (inst) {
  return '.' + inst._rootNodeID;
};

распределение событий

Поскольку мероприятие было делегировано зарегистрированным наdocumentВкл., то при срабатывании события обязательно нужен процесс распределения события, и процесс тоже очень простой, так как событие хранится вlistenrBank, тогда мне просто нужно найти соответствующий тип события, а затем выполнить обратный вызов события, хорошо

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

Сначала найдите триггер событияDOMа такжеReact Component, по-прежнему легко найти настоящий DOM, висходный код getEventTargetможно увидеть в:

// 源码看这里: https://github.com/facebook/react/blob/master/packages/react-dom/src/events/ReactDOMEventListener.js#L419
const nativeEventTarget = getEventTarget(nativeEvent)
let targetInst = getClosestInstanceFromNode(nativeEventTarget)
function getEventTarget(nativeEvent) {
  let target = nativeEvent.target || nativeEvent.srcElement || window

  if (target.correspondingUseElement) {
    target = target.correspondingUseElement
  }

  return target.nodeType === TEXT_NODE ? target.parentNode : target
}

этоnativeEventTargetОбъект подвешен на__reactInternalInstanceатрибут в начале, этот атрибутinternalInstanceKey, чья ценность является компонентом реагирования, соответствующий текущему экземпляру реагирования

Продолжайте смотреть исходный код:dispatchEventForPluginEventSystem()

function dispatchEventForPluginEventSystem(
  topLevelType: DOMTopLevelEventType,
  eventSystemFlags: EventSystemFlags,
  nativeEvent: AnyNativeEvent,
  targetInst: null | Fiber
): void {
  const bookKeeping = getTopLevelCallbackBookKeeping(
    topLevelType,
    nativeEvent,
    targetInst,
    eventSystemFlags
  )

  try {
    // Event queue being processed in the same cycle allows
    // `preventDefault`.
    batchedEventUpdates(handleTopLevel, bookKeeping)
  } finally {
    releaseTopLevelCallbackBookKeeping(bookKeeping)
  }
}

Ты это видел?batchedEventUpdates()Пакетное обновление, его задача состоит в том, чтобы поместить текущее инициированное событие в пакетную очередь.handleTopLevel — ядро ​​распределения событий

👉 Исходный код здесь:handleTopLevel

function handleTopLevel(bookKeeping: BookKeepingInstance) {
  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) {
      const ancestors = bookKeeping.ancestors
      ;((ancestors: any): Array<Fiber | null>).push(ancestor)
      break
    }
    const root = findRootContainerNode(ancestor)
    if (!root) {
      break
    }
    const tag = ancestor.tag
    if (tag === HostComponent || tag === HostText) {
      bookKeeping.ancestors.push(ancestor)
    }
    ancestor = getClosestInstanceFromNode(root)
  } while (ancestor)
}

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

затем продолжить этот метод

for (let i = 0; i < bookKeeping.ancestors.length; i++) {
  targetInst = bookKeeping.ancestors[i]
  // getEventTarget上边有讲到
  const eventTarget = getEventTarget(bookKeeping.nativeEvent)
  const topLevelType = ((bookKeeping.topLevelType: any): DOMTopLevelEventType)
  const nativeEvent = ((bookKeeping.nativeEvent: any): AnyNativeEvent)

  runExtractedPluginEventsInBatch(
    topLevelType,
    targetInst,
    nativeEvent,
    eventTarget,
    bookKeeping.eventSystemFlags
  )
}

Вот цикл for для перебора этого компонента React и всех его родительских компонентов, а затем выполненияrunExtractedPluginEventsInBatch()метод

Как видно из распределения событий выше, в самом React реализован механизм всплытия. Начиная с объекта, вызвавшего событие, двигайтесь в обратном направлении к родительским элементам, по очереди вызывая их зарегистрированные обратные вызовы событий.

выполнение события

упомянутый вышеrunExtractedPluginEventsInBatch()Метод является точкой входа для выполнения события.Из исходного кода мы можем знать, что он делает две вещи.

👉Исходный код runExtractedPluginEventsInBatch

  • Создание синтетических событий
  • Пакетные синтетические события
export function runExtractedPluginEventsInBatch(
  topLevelType: TopLevelType,
  targetInst: null | Fiber,
  nativeEvent: AnyNativeEvent,
  nativeEventTarget: EventTarget,
  eventSystemFlags: EventSystemFlags
) {
  // step1 : 构造合成事件
  const events = extractPluginEvents(
    topLevelType,
    targetInst,
    nativeEvent,
    nativeEventTarget,
    eventSystemFlags
  )

  // step2 : 批处理
  runEventsInBatch(events)
}

Создание синтетических событий

Давайте посмотрим на соответствующий кодextractPluginEvents()а такжеrunEventsInBatch()

function extractPluginEvents(
  topLevelType: TopLevelType,
  targetInst: null | Fiber,
  nativeEvent: AnyNativeEvent,
  nativeEventTarget: EventTarget,
  eventSystemFlags: EventSystemFlags
): Array<ReactSyntheticEvent> | ReactSyntheticEvent | null {
  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,
        eventSystemFlags
      )
      if (extractedEvents) {
        events = accumulateInto(events, extractedEvents)
      }
    }
  }
  return events
}

Сначала пройдитеplugins, соответствующий код находится в:исходный код плагинов, этот plugins представляет собой массив коллекций всех плагинов синтеза событий, эти плагины находятся вEventPluginHubвнедряется во время инициализации

// 📢 源码地址 : https://github.com/facebook/react/blob/master/packages/legacy-events/EventPluginHub.js#L80

export const injection = {
  injectEventPluginOrder,
  injectEventPluginsByName
}
// 📢 源码地址 : https://github.com/facebook/react/blob/master/packages/react-dom/src/client/ReactDOMClientInjection.js#L26
EventPluginHubInjection.injectEventPluginOrder(DOMEventPluginOrder)

EventPluginHubInjection.injectEventPluginsByName({
  SimpleEventPlugin: SimpleEventPlugin,
  EnterLeaveEventPlugin: EnterLeaveEventPlugin,
  ChangeEventPlugin: ChangeEventPlugin,
  SelectEventPlugin: SelectEventPlugin,
  BeforeInputEventPlugin: BeforeInputEventPlugin
})

Стоп, не буду тут анализ начинать, давайте дальше посмотримextractEventsлогический код

const extractedEvents = possiblePlugin.extractEvents(
  topLevelType,
  targetInst,
  nativeEvent,
  nativeEventTarget,
  eventSystemFlags
)
if (extractedEvents) {
  events = accumulateInto(events, extractedEvents)
}

потому чтоconst possiblePlugin: PluginModule = plugins[i], тип PluginModule и мы можем перейти к 👉Исходный код SimpleEventPluginпосмотриextractEventsчто ты сделал

extractEvents: function() {
  const dispatchConfig = topLevelEventsToDispatchConfig[topLevelType]
  if (!dispatchConfig) {
    return null
  }
  //...
}

Во-первых, посмотрите наtopLevelEventsToDispatchConfigВ этом объекте имеется свойство Topleveltype в этом объекте, если это означает, что текущее событие можно использоватьSimpleEventPluginСоздание синтетических событий

определяется в функцииEventConstructor, то черезswitch...caseзаявление о назначении

extractEvents: function() {
  //...
  let EventConstructor
  switch (topLevelType) {
    // ...
    case DOMTopLevelEventTypes.TOP_POINTER_UP:
      EventConstructor = SyntheticPointerEvent
      break
    default:
      EventConstructor = SyntheticEvent
      break
  }
}

Короче говоря, назначениеEventConstructor, если вы хотите узнать большеSyntheticEvent,пожалуйста, нажмите здесь

задаватьEventConstructorПосле этого метод продолжает выполняться

extractEvents: function() {
  //...
  const event = EventConstructor.getPooled(
    dispatchConfig,
    targetInst,
    nativeEvent,
    nativeEventTarget
  )
  accumulateTwoPhaseDispatches(event)
  return event
}

Смысл этого фрагмента кода в том, чтобы вынуть синтетическое событие из пула объектов событий, гдеgetPooled()Метод на самом делеSyntheticEventОн устанавливается при инициализации, давайте посмотрим на код

function addEventPoolingTo(EventConstructor) {
  EventConstructor.eventPool = []
  // 就是这里设置了getPooled
  EventConstructor.getPooled = getPooledEvent
  EventConstructor.release = releasePooledEvent
}

SyntheticEvent.extend = function(Interface) {
  //...
  addEventPoolingTo(Class)

  return Class
}

addEventPoolingTo(SyntheticEvent)

Видя здесь, мы знаем,getPooledто естьgetPooledEvent, тогда посмотримgetPooledEventчто ты сделал

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
  )
}

Прежде всего, я пойду к пулу объектов, чтобы увидеть, равна ли длина 0. Если это первый раз, когда событие запускается, извините, вам нужноnew EventConstructorТеперь, если событие позже сработает снова, оно будет взято прямо из пула объектов, то есть напрямуюinstance = EventConstructor.eventPool.pop()Все кончено

хорошо, пока поговорим об этом, давайте перейдем к другой важной операции, выполняемой событием:Пакетный запускEventsInBatch(события)

пакетная обработка

Пакетная обработка в основном осуществляется черезrunEventQueueInBatch(events)Для работы давайте взглянем на исходный код: 👉Исходный код runEventQueueInBatch

export function runEventsInBatch(
  events: Array<ReactSyntheticEvent> | ReactSyntheticEvent | null
) {
  if (events !== null) {
    eventQueue = accumulateInto(eventQueue, events)
  }

  // Set `eventQueue` to null before processing it so that we can tell if more
  // events get enqueued while processing.
  const processingEventQueue = eventQueue
  eventQueue = null

  if (!processingEventQueue) {
    return
  }

  forEachAccumulated(processingEventQueue, executeDispatchesAndReleaseTopLevel)
  invariant(
    !eventQueue,
    'processEventQueue(): Additional events were enqueued while processing ' +
      'an event queue. Support for this has not yet been implemented.'
  )
  // This would be a good time to rethrow if any of the event handlers threw.
  rethrowCaughtError()
}

Этот метод сначала вызовет текущие события, которые необходимо обработать, и очередь, которая не была обработана ранее.accumulateIntoМетоды объединяются по порядку и объединяются в новую очередь

еслиprocessingEventQueueЭто пусто, gg, нет обработанного события, выходим, иначе звонимforEachAccumulated(), см. исходный код здесь:forEachAccumulated исходный код

function forEachAccumulated<T>(
  arr: ?(Array<T> | T),
  cb: (elem: T) => void,
  scope: ?any
) {
  if (Array.isArray(arr)) {
    arr.forEach(cb, scope)
  } else if (arr) {
    cb.call(scope, arr)
  }
}

Этот метод заключается в том, чтобы сначала просмотреть очередь событийprocessingEventQueueЭто массив? Если это массив, это означает, что в очереди более одного события, а затем пройти по очереди и вызватьexecuteDispatchesAndReleaseTopLevel, иначе это означает, что в очереди только одно событие, его можно вызвать напрямую без обхода

📢Исходный код executeDispatchesAndReleaseTopLevel

const executeDispatchesAndRelease = function(event: ReactSyntheticEvent) {
  if (event) {
    executeDispatchesInOrder(event)

    if (!event.isPersistent()) {
      event.constructor.release(event)
    }
  }
}
const executeDispatchesAndReleaseTopLevel = function(e) {
  return executeDispatchesAndRelease(e)
}
export function executeDispatchesInOrder(event) {
  const dispatchListeners = event._dispatchListeners
  const dispatchInstances = event._dispatchInstances
  if (__DEV__) {
    validateEventDispatches(event)
  }
  if (Array.isArray(dispatchListeners)) {
    for (let i = 0; i < dispatchListeners.length; i++) {
      if (event.isPropagationStopped()) {
        break
      }
      // Listeners and Instances are two parallel arrays that are always in sync.
      executeDispatch(event, dispatchListeners[i], dispatchInstances[i])
    }
  } else if (dispatchListeners) {
    executeDispatch(event, dispatchListeners, dispatchInstances)
  }
  event._dispatchListeners = null
  event._dispatchInstances = null
}

Сначала смонтируйте его на полученное событиеdispatchListeners, представляет собой набор всех зарегистрированных функций обратного вызова события, обходят этот набор, еслиevent.isPropagationStopped() = ture, ладно, перерыв в порядке, потому что событие, инициированное до того, как это было вызваноevent.stopPropagation(), значение isPropagationStopped установлено равным true, текущее событие и последующие события в качестве родительских событий больше не должны выполняться.

Здесь, когда event.isPropagationStopped() имеет значение true, выполнение восходящего обхода синтетического события прерывается, что имеет тот же эффект, что и вызов stopPropagation для собственного события. Если цикл не прерывается, продолжить выполнениеexecuteDispatchметод, так как для этого метода предлагается адрес исходного кода:адрес источника executeDispatch

а также...

следовать за

Продолжения нет, и писать больше не могу.Далее идем в исходники сами.С полудня смотрим яму,а потом проходимevent.nativeEvent.stopImmediatePropagationПосле решения проблемы я начал читать соответствующие сообщения в блоге и читать исходный код.Я был взорван.Я наблюдал за этой штукой с 14:00 до 22:00.Меня уже стошнило, ОМГ

подсказки: при срабатывании связанного onClick срабатывает синтетическое событие, а синтетическое событие будет позже, чем нативное, поэтому при клике, по сути, всплыло нативное событие, которое привязано к нативному событию. документ. Событие запускает, а затем запускает синтетическое событие. В настоящее время уже слишком поздно организовывать синтетическое событие.

Так что просто измените document.body.addEventListener на window.addEventListener.

Другие статьи

Ссылки по теме