Как работает система событий React

внешний интерфейс React.js
Как работает система событий React

Автор этой статьи:речная вода

предисловие

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

существуетВведение в React-событияВ этой статье представлены синтетические объекты событий и причины их предоставления. Основная причина заключается в том, что React хочет реализовать полнобраузерную структуру. Для достижения этой цели необходимо предоставить совместимую с полноценным браузером систему событий для плавного из разных браузеров различия устройств.

Синтетический объект события очень интересен, сначала вы почувствуете себя странно, когда услышите название, но еще более странно, когда увидите английское название.SyntheticEvent, на самом деле синтез событий означает синтез события React с использованием собственных событий, таких как использование собственных событий.clickсобытие синтезированоonClickсобытия, используя роднойmouseoutсобытие синтезированоonMouseLeaveСобытия, местное событие и большинство из них являются одним из синтетических типов события, и только когда дело доходит до проблем совместимости, нам необходимо использовать, событие не соответствует синтезу. Синтетический инцидент React не был первым, проблемы, возникающие, возникшие в 300 мс на iOS, представили FastClick с использованием сенсорного события, синтезированные событию Click, также считали приложением синтетического события.

Поняв, что события React являются синтетическими событиями, мы посмотрим на события с другой точки зрения, например, на код, который мы часто пишем в коде.

<button onClick={handleClick}>
  Activate Lasers
</button>

мы уже знаем этоonClickЭто просто синтетическое событие, а не нативное, так что же произошло за это время? Как соотносятся нативные события и синтетические события?

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

  1. привязка события
  2. триггер события

Давайте посмотрим, как работают эти два этапа.Здесь мы в основном анализируем уровень исходного кода и берем содержимое исходного кода 16.13 в качестве эталона.

1. Как React связывает события?

Поскольку React предоставляет синтетические события, вам нужно знать, как синтетические события соотносятся с нативными событиями.Это соответствие хранится в плагине событий React.EventPlugin, Подключаемые модули событий можно рассматривать как React, инкапсулирующие различные функции обработки синтетических событий в модуль, каждый модуль обрабатывает только свои собственные соответствующие синтетические события, так что различные типы событий могут быть отделены в коде, например, таргетингonChangeСобытия имеют отдельныйLegacyChangeEventPluginплагин для обработки, дляonMouseEnter,onMouseLeaveиспользоватьLegacyEnterLeaveEventPluginплагин для обработки.

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

injectEventPluginsByName({
    SimpleEventPlugin: LegacySimpleEventPlugin,
    EnterLeaveEventPlugin: LegacyEnterLeaveEventPlugin,
    ChangeEventPlugin: LegacyChangeEventPlugin,
    SelectEventPlugin: LegacySelectEventPlugin,
    BeforeInputEventPlugin: LegacyBeforeInputEventPlugin
});

После регистрации вышеупомянутого плагина,EventPluginRegistry(В старой версии кода этот модуль называетсяEventPluginHub) В этом модуле инициализируются некоторые глобальные объекты.Есть несколько объектов более важных и их можно обсудить отдельно.

Первый объектregistrationNameModule, который содержит сопоставление событий React с соответствующим плагином. Это примерно так. Он содержит все типы событий, поддерживаемые React. Самая большая функция этого объекта — определить, является ли реквизит компонента типом события, который используется при обработке собственных реквизитов Компонента, если реквизит в этом объекте будет рассматриваться как событие.

{
    onBlur: SimpleEventPlugin,
    onClick: SimpleEventPlugin,
    onClickCapture: SimpleEventPlugin,
    onChange: ChangeEventPlugin,
    onChangeCapture: ChangeEventPlugin,
    onMouseEnter: EnterLeaveEventPlugin,
    onMouseLeave: EnterLeaveEventPlugin,
    ...
}

Второй объектregistrationNameDependencies, этот объект выглядит следующим образом

{
    onBlur: ['blur'],
    onClick: ['click'],
    onClickCapture: ['click'],
    onChange: ['blur', 'change', 'click', 'focus', 'input', 'keydown', 'keyup', 'selectionchange'],
    onMouseEnter: ['mouseout', 'mouseover'],
    onMouseLeave: ['mouseout', 'mouseover'],
    ...
}

Этот объект является отображением синтетических событий на нативные события, о которых мы упоминали в начале.onClickа такжеonClickCaptureСобытия, полагайтесь только на собственныеclickмероприятие. Но дляonMouseLeaveОн опирается на дваmouseout,mouseover, что указывает на то, что это событие используется Reactmouseoutа такжеmouseoverАналоговый синтез. Именно из-за этого поведения, даже если он делает реактивный, способный синтезировать многие браузеры, не поддерживают событие для нашего кода для использования.

Третий объектplugins, этот объект представляет собой список всех плагинов, зарегистрированных выше.

plugins = [LegacySimpleEventPlugin, LegacyEnterLeaveEventPlugin, ...];

Прочитав вышеизложенную информацию, давайте развернёмся и посмотрим на очередной рядовойEventPluginНа что это похоже. Плагин — это объект, который содержит следующие два свойства.

// event plugin
{
  eventTypes, // 一个数组,包含了所有合成事件相关的信息,包括其对应的原生事件关系
  extractEvents: // 一个函数,当原生事件触发时执行这个函数
}

Знание приведенной выше информации будет очень полезно для нас, чтобы проанализировать принцип работы событий React.Давайте начнем фазу привязки событий.

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

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

  1. пройти черезregistrationNameDependenciesПроверьте, от чего зависит это событие Reactродной тип события.

  1. Проверьте, зарегистрированы ли один или несколько из этих собственных типов событий, и проигнорируйте их.

  1. Если собственный тип события не был зарегистрирован, зарегистрируйте собственное событие вdocumentвыше, обратный вызов предоставляется ReactdispatchEventфункция.

Вышеприведенное описание этапа:

  1. Мы регистрируем все типы событий наdocumentначальство.
  2. Все встроенные прослушиватели событийdispatchEventфункция.
  3. Один и тот же тип события React будет связывать нативное событие только один раз, например, независимо от того, сколько мы пишемonClick, конечная реакция будет иметь только одну на событие DOMlistener.
  4. React не использует нашу бизнес-логикуlistenerПривязан к оригинальному событию, ни поддерживать подобноеeventlistenermapвещи для хранения нашихlistener.

Из 3-4 правил можно сделать вывод, что наша бизнес-логикаlistenerЭто вообще не имеет никакого отношения к фактическому событию DOM. React только гарантирует, что это собственное событие может быть захвачено само по себе, а затем React отправит обратный вызов нашего события. Когда наша страница переключается, React ничего не может сделать, поэтому свободен от операцияremoveEventListenerили синхронизироватьeventlistenermapСледовательно, эффективность его выполнения будет значительно повышена, что эквивалентно глобальному делегированию событий.Даже если отрисовывается большой список, разработчику не нужно заботиться о проблеме привязки событий.

2. Как React запускает события?

Мы знаем, что, поскольку все типы событий связаны с ReactdispatchEventФункция, так что вы можете управлять некоторыми общими поведениями в глобальном масштабе.Ниже приведен весь процесс поведения.

export function dispatchEventForLegacyPluginEventSystem(
  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);
  }
}

bookKeepingОн сохраняется для иерархической взаимосвязи компонентов при выполнении события, то есть, если структура компонента изменится в процессе выполнения события, это не повлияет на процесс срабатывания события.

Весь процесс триггерного события выглядит следующим образом:

  1. Любое событие срабатывает, выполняетсяdispatchEventфункция.
  2. dispatchEventвоплощать в жизньbatchedEventUpdates(handleTopLevel),batchedEventUpdatesвключит переключатель пакетного рендеринга и вызоветhandleTopLevel.
  3. handleTopLevelбудет выполняться последовательноpluginsВсе плагины событий в.
  4. Если плагин определяет тип события, которое ему нужно обработать, он обрабатывает это событие.

Для большинства событий логика обработки следующая, т.е.LegacySimpleEventPluginПлагины делают свою работу

  1. Какой синтетический тип события использовать, определяется собственным типом события (объектом-оболочкой собственного события, напримерSyntheticMouseEvent).
  2. Если в пуле объектов есть экземпляр этого типа, извлеките экземпляр, перезапишите его свойства и используйте его в качестве объекта события, отправленного на этот раз (повторное использование объекта события), если нет, создайте новый экземпляр.

  1. Найдите соответствующий узел DOM из нативного события click, найдите ближайший экземпляр компонента React из узла DOM и найдите цепочку, образованную родительским узлом этого экземпляра вверх.Эта цепочка — это цепочка, которую мы хотим вызвать синтетическое событие. , ( содержит только компоненты нативного типа,div,aтакие родные компоненты).

  1. Запустите эту цепочку в обратном порядке, родитель -> ребенок, смоделируйте фазу захвата, запустите все реквизиты, содержащиеonClickCaptureпример.

  1. Запустите эту цепочку вперед, ребенок -> родитель, смоделируйте фазу всплытия, запустите все реквизиты, содержащиеonClickпример.

Эти этапы иллюстрируют следующие явления:

  1. Синтетические события React можно использовать только в цикле событий, потому что этот объект, скорее всего, будет повторно использоваться другими этапами, если вы хотите сохранить его, вам нужно вызвать его вручную.event.persist()Скажите React, что этот объект должен быть сохранен. (Устарело в React17)
  2. React пузырится и ловит на самом деле не пузырится и не ловится на уровне DOM
  3. React активирует все соответствующие узлы в нативном событии.onClickсобытия, которые выполняют этиonClickРаньше React включал переключатель пакетного рендеринга, этот переключательsetStateв асинхронную функцию.
  4. События вступают в силу только для собственных компонентов, пользовательские компоненты не срабатывают.onClick.

3. Что мы узнали из системы событий React

  1. React16 связывает нативные события сdocumentначальство.

Это легко понять, события React на самом деле находятся вdocumentсрабатывает.

  1. Полученный нами объект события является синтетическим событием React, и объект события нельзя использовать вне события.

Итак, следующее неправильное использование

function onClick(event) {
    setTimeout(() => {
        console.log(event.target.value);
    }, 100);
}
  1. React включит пакетные обновления при отправке события, после чего всеsetStateстать асинхронным.
function onClick(event) {
    setState({a: 1}); // 1
    setState({a: 2}); // 2
    setTimeout(() => {
        setState({a: 3}); // 3
        setState({a: 4}); // 4
    }, 0);
}

В настоящее время 1 и 2 являются асинхронными в событии, оба они запускают операцию рендеринга только один раз, 3 и 4 синхронны, а 3 и 4 каждый запускает рендеринг.

  1. React onClick/onClickCapture, на самом деле все они происходят в фазе всплытия исходного события.
document.addEventListener('click', console.log.bind(null, 'native'));

function onClickCapture() {
    console.log('capture');
}

<div onClickCapture={onClickCapture}/>

Здесь мы используемonClickCapture, но на самом деле он все еще всплывает для нативных событий, поэтому React 16 на самом деле не поддерживает события захвата привязки.

  1. Поскольку все события регистрируются в событии верхнего уровня,ReactDOM.renderБудут конфликты.

Если мы визуализируем поддерево, созданное с использованием другой версии экземпляра React, то даже если поддерево вызываетсяe.stopPropagatioСобытия продолжают распространяться. Таким образом, несколько версий React конфликтуют с событиями.

Наконец, мы можем легко понять архитектурную схему системы событий React.

4. Что нового в системе событий в React 17

Сейчас выпущен React 17, официально названный обновлением без новых функций.Для пользователей он не предоставляет взрывных функций, таких как Hooks, и не имеет серьезных рефакторингов, таких как Fiber, но накопил множество исправлений ошибок и исправил предыдущие. Есть много недостатков. Самым большим изменением является трансформация системы событий.

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

Настроен для связывания события верхнего уровня к контейнеру, Reactom.runder (приложение, контейнер);

react_17_delegation

Привязать событие верхнего уровня кcontainerна вместоdocumentЭто может решить проблему сосуществования нескольких версий, с которой мы столкнулись, что является основным преимуществом решения с микроинтерфейсом.

Выравнивание нативных событий браузера

React 17 наконец-то поддерживает встроенную поддержку событий захвата в соответствии с собственными стандартами браузера.

в то же времяonScrollСобытия больше не всплывают.

onFocusа такжеonBlurиспользовать роднойfocusin,focusoutсинтез.

Aligning with Browsers

Мы внесли несколько небольших изменений, связанных с системой событий: Событие onScroll больше не всплывает, чтобы предотвратить общую путаницу. События React onFocus и onBlur переключились на использование встроенных событий focusin и focusout, которые более точно соответствуют существующему поведению React и иногда предоставляют дополнительную информацию. События фазы захвата (например, onClickCapture) теперь используют настоящие прослушиватели фазы захвата браузера.

Отменить мультиплексирование событий

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

Ссылаться на

  1. реагировать JS.org/docs/events…
  2. реагировать JS.org/docs/winter…
  3. GitHub.com/Facebook/Горячие…

Эта статья была опубликована сКоманда внешнего интерфейса NetEase Cloud Music, Любое несанкционированное воспроизведение статьи запрещено. Мы набираем front-end, iOS и Android круглый год.Если вы готовы сменить работу и любите облачную музыку, присоединяйтесь к нам на grp.music-fe(at)corp.netease.com!