React 17 делегирует события к root вместо document

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

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

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

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

Вот пример одной из таких проблем.

В приведенном ниже примере мы взяли простой компонент React, который выводит в лог событие при нажатии на кнопку Click и отображается в div с идентификатором react-root. Контейнер React DOM завернут в div с id main, у которого есть событие change, содержащее stopPropagation().

// Div событие change содержит stopPropagation()
<div id="main">
  // Div где реакт компонент будет отображен
  <div id="react-root"></div>
</div>
function App() {
  const handleClick = (e) => {
    console.log(e);
  };
  return (
    <div className="App">
      <h1>Example</h1>
      <button onClick={handleClick}>Click</button>
    </div>
  );
}
ReactDOM.render(<App />, document.getElementById('react-root'));

Присоединение события change к div#main

document.getElementById("main").addEventListener(
  "change",
  function (e) {
    e.stopPropagation();
  },
  false
);

При нажатии на кнопку событие в консоли не появляется.

Причина в том, что обработчик onChange прикреплён к узлу document, а e.stopPropagation() на div#main перехватывает всплывающее событие раньше.

Чтобы исправить такие проблемы, React 17 больше не подключает обработчики событий на уровне document. Вместо этого он прикрепляет их к корневому контейнеру DOM, в котором отображается дерево React.

Изменения в React 17

После изменений в React 17 события прикрепляются к корневому контейнеру DOM, в котором отображается дерево React. В нашем примере событие будет прикреплено к div с идентификатором react-root. Поэтому наше событие будет инициировано, когда будет нажата кнопка.

Заметка

Релиз-кандидат React 17 можно установить отсюда.

Ознакомьcя с предыдущим обсуждением делегирования событий здесь и pull request здесь.

Итог

Это изменение хорошо показывает, что даже небольшой сдвиг в инфраструктурном поведении React может заметно упростить жизнь на больших и смешанных кодовых базах. Для повседневной разработки это не «новый API», а тихое улучшение предсказуемости.

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