Примечания о смешивании синтетических событий React и собственных событий DOM

внешний интерфейс JavaScript React.js

Ссылочный код React, версия - ветка v15.6.1.

Реагировать на синтетические события

Почему существует абстракция синтетических событий?

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

принцип

В React, если нам нужно привязать события, мы часто пишем это в jsx:

<div onClick={this.onClick}>
	react事件
</div>

Принцип примерно такой:

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

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

Среди них, поскольку объект события мультиплексирован, свойства будут очищены после выполнения функции обработки события, поэтому асинхронный доступ к свойствам события невозможен.event-pooling.

Как использовать нативные события в React

В то время как React инкапсулирует почти все нативные события, такие как:

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

При ожидании сценариев вы должны использовать собственные события для обработки бизнес-логики.

Поскольку нативные события должны быть привязаны к реальному DOM, они обычноcomponentDidMount阶段/ref的函数执行阶段Чтобы связать операцию, вcomponentWillUnmount阶段Отмените привязку, чтобы избежать утечек памяти.

Пример выглядит следующим образом:

class Demo extends React.PureComponent {
    componentDidMount() {
        const $this = ReactDOM.findDOMNode(this)
        $this.addEventListener('click', this.onDOMClick, false)
    }

    onDOMClick = evt => {
        // ...
    }

    render() {
        return (
            <div>Demo</div>
        )
    }
}

Смешивание синтетических и нативных событий

Если вам нужно смешивать синтетические события и собственные события в бизнес-сценариях, вам нужно обратить внимание на следующие моменты во время использования:

Порядок ответа

Давайте посмотрим на простой пример, нажмите на следующий примерDemoКак будет выглядеть консольный вывод в будущем?

class Demo extends React.PureComponent {
    componentDidMount() {
        const $this = ReactDOM.findDOMNode(this)
        $this.addEventListener('click', this.onDOMClick, false)
    }

    onDOMClick = evt => {
        console.log('dom event')
    }
    
    onClick = evt => {
        console.log('react event')
    }

    render() {
        return (
            <div onClick={this.onClick}>Demo</div>
        )
    }
}

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

То есть окончательный вывод консоли:

dom event react event

перестань пузыриться

Тогда, если вonDOMClickвызыватьevt.stopPropagation()Шерстяная ткань?

Поскольку события DOM защищены от всплытия и не могут достичь документа, синтетическое событие не будет запущено, а вывод консоли будет выглядеть так:

dom event

Простые примеры легче понять, а примеры сложнее:

class Demo extends React.PureComponent {
    componentDidMount() {
        const $parent = ReactDOM.findDOMNode(this)
        const $child = $parent.querySelector('.child')
        
        $parent.addEventListener('click', this.onParentDOMClick, false)
        $child.addEventListener('click', this.onChildDOMClick, false)
    }

    onParentDOMClick = evt => {
        console.log('parent dom event')
    }
    
    onChildDOMClick = evt => {
        console.log('child dom event')
    }    
    
    onParentClick = evt => {
        console.log('parent react event')
    }

    onChildClick = evt => {
        console.log('child react event')
    }

    render() {
        return (
            <div onClick={this.onParentClick}>
                <div className="child" onClick={this.onChildClick}>
                    Demo
                </div>
            </div>
        )
    }
}

если вonChildClickвызыватьevt.stopPropagtion(), вывод консоли становится следующим:

child dom event parent dom event child react event

Этот результат связан с тем, что функция stopPropagation, которая инкапсулирует синтетическое событие React, добавляет к себе флаг isPropagationStopped при вызове, чтобы определить, выполняются ли последующие слушатели.

Исходный код выглядит следующим образом:

// https://github.com/facebook/react/blob/v15.6.1/src/renderers/shared/stack/event/EventPluginUtils.js
for (var i = 0; i < dispatchListeners.length; i++) {
  if (event.isPropagationStopped()) {
    break;
  }
  // Listeners and Instances are two parallel arrays that are always in sync.
  if (dispatchListeners[i](event, dispatchInstances[i])) {
    return dispatchInstances[i];
  }
}

Неловкое положение nativeEvent в системе событий React

У некоторых людей могут возникнуть сомнения, хотя синтетическое событие находится позже, чем нативное событие в последовательности ответа, может ли синтетическое событие повлиять на выполнение слушателя нативного события? Ответ (почти) невозможен. . .

Мы знаем, что входные параметры, полученные в прослушивателях событий React, не являются нативными событиями браузера, а нативные события можно передавать черезevt.nativeEventчтобы получить. Но, к сожалению, nativeEvent делает очень мало.

stopPropagation

В ожиданиях пользователей,stopPropagationиспользуется для предотвращения всплытия собственного события текущего DOM.

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

stopImmediatePropagation

stopImmediatePropagationЧасто используется для предотвращения ненужного выполнения в нескольких прослушивателях событий при смешивании нескольких сторонних библиотек.

Но в системе React компонент может быть привязан только к одному обработчику событий одного и того же типа (при повторном определении более поздний обработчик перезапишет предыдущий), поэтому синтетические события даже не инкапсулируются.stopImmediatePropagation.

На самом деле nativeEventstopImmediatePropagationТолько прослушиватели событий, привязанные к документу, могут быть заблокированы. Кроме того, посколькуПроблема с порядком привязки событий, следует отметить, что еслиreact-dom.jsСобытие документа связано перед загрузкой,stopImmediatePropagationЭто также не остановить.

[Непопулярно] Синтетические события на этапе захвата

Документации по синтетическим событиям много, и нужно найти что-то новое. . .

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

Слегка изменим этот пример, как теперь будет выглядеть вывод консоли?

class Demo extends React.PureComponent {
    componentDidMount() {
        const $parent = ReactDOM.findDOMNode(this)
        const $child = $parent.querySelector('.child')
        
        $parent.addEventListener('click', this.onParentDOMClick, true)
        $child.addEventListener('click', this.onChildDOMClick, false)
    }

    onParentDOMClick = evt => {
        console.log('captrue: parent dom event')
    }
    
    onChildDOMClick = evt => {
        console.log('bubble: child dom event')
    }    
    
    onParentClick = evt => {
        console.log('capture: parent react event')
    }

    onChildClick = evt => {
        console.log('bubble: child react event')
    }

    render() {
        return (
            <div onClickCapture={this.onParentClick}>
                <div className="child" onClick={this.onChildClick}>
                    Demo
                </div>
            </div>
        )
    }
}

оказаться:

captrue: parent dom event bubble: child dom event capture: parent react event bubble: child react event

Это кажется разумным, но это не кажется разумным. Некоторые люди (такие как я) могут быть сбиты с толку, почему фазовая реакция захвата синтетических событий также позже, чем фазовая реакция пузырьков нативных событий?

На самом деле это происходит потому, что прокси синтетического события не регистрирует обработчики событий в фазе захвата/бублинга в документе одновременно, а используются только обработчики событий в фазе всплытия.Каждый раз, когда DOM событие запускается, React будетevent._dispatchListenersВставьте все функции, которые необходимо выполнить, а затем выполните их в цикле (как в исходном коде React выше).

а также_dispatchListenersЛогика генерации следующая:

// https://github.com/facebook/react/blob/v15.6.1/src/renderers/dom/client/ReactDOMTreeTraversal.js
/* 
    path为react的组件树,由下向上遍历,本例中就是[child, parent];
    然后先将标记为captured的监听器置入_dispatchListeners,此时顺序是path从后往前;
    再是标记为bubbled的监听器,顺序是从前往后。
*/
function traverseTwoPhase(inst, fn, arg) {
  var path = [];
  while (inst) {
    path.push(inst);
    inst = inst._hostParent;
  }
  var i;
  for (i = path.length; i-- > 0; ) {
    fn(path[i], 'captured', arg);
  }
  for (i = 0; i < path.length; i++) {
    fn(path[i], 'bubbled', arg);
  }
}

В заключение

  1. Прослушиватели синтетических событий единообразно регистрируются в документе и имеют только фазу всплытия. Таким образом, встроенные прослушиватели событий всегда отвечают раньше, чем синтетические прослушиватели событий.
  2. После предотвращения всплытия нативного события это предотвратит выполнение прослушивателя синтетического события.
  3. Синтетическое событие наносительно в этом сценарии, нет шерсти