合成事件概述

如果各位看了react的源码,会发现在事件处理的代码里,有那么一段注释:

/**
 * Summary of `ReactBrowserEventEmitter` event handling:
 *
 *  - Top-level delegation is used to trap most native browser events. This
 *    may only occur in the main thread and is the responsibility of
 *    ReactDOMEventListener, which is injected and can therefore support
 *    pluggable event sources. This is the only work that occurs in the main
 *    thread.
 *
 *  - We normalize and de-duplicate events to account for browser quirks. This
 *    may be done in the worker thread.
 *
 *  - Forward these native events (with the associated top-level type used to
 *    trap it) to `EventPluginHub`, which in turn will ask plugins if they want
 *    to extract any synthetic events.
 *
 *  - The `EventPluginHub` will then process each event by annotating them with
 *    "dispatches", a sequence of listeners and IDs that care about that event.
 *
 *  - The `EventPluginHub` then dispatches the events.
 *
 * Overview of React and the event system:
 *
 * +------------+    .
 * |    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
 */

来一步步的解析,让这货更明朗起来。

Top-level delegation is used to trap most native browser events. This may only occur in the main thread and is the responsibility of ReactDOMEventListener, which is injected and can therefore support pluggable event sources. This is the only work that occurs in the main thread.

React为每个事件类型使用一个事件侦听器来调用虚拟DOM中所有提交的处理程序。看看下面的这个例子:

const ExampleComponent = () => (
  <div onClick={onClick}>
    <div onClick={onClick} />
  </div>
)

这样我们就将在原生DOM上为click事件注册一个事件侦听器。我们在Chrome开发工具上运行getEventListeners(document.querySelector('button'))方法,我们将得到类似的以下结果:

{click: Array(n)} //n is number

每个事件类型的监听器都会在每个渲染周期中得到保证,所以如果我们定义额外的keydown类型的事件处理程序,我们会得到如下输出:

{click: Array(n), keydown: Array(1)}

We normalize and de-duplicate events to account for browser quirks. This may be done in the worker thread.

对于每个浏览器,无论其实现如何,都将拥有一致的事件参数,因为React将它们规范化。 由于 React 为多个处理程序注册了单个事件侦听器, 因此它需要为每个处理程序重新分配事件。

Forward these native events (with the associated top-level type used to trap it) to EventPluginHub, which in turn will ask plugins if they want to extract any synthetic events.

EventPluginHub是React事件处理系统中的一个非常核心的组件。这就是将所有事件插件统一到一个位置的方法,并将被分派的事件重定向到每个事件。每个插件负责提取和处理不同的事件类型,例如,有SimpleEventPlugin来处理可能在大多数浏览器中实现的事件,比如鼠标事件和按键(源码); 还有ChangeEventPlugin,它就是处理大家都知道的onChange事件(源码)。

合成事件是React的规范化事件参数,它确保所有浏览器之间的一致性,并由插件生成。注意,合成事件正在被合并!这意味着相同的对象实例在多个处理程序中使用,只是在每次调用前都使用新属性重置它,然后处理:

function onClick(event) {
  console.log(event); // => nullified object.
  console.log(event.type); // => "click"
  const eventType = event.type; // => "click"
  setTimeout(function() {
    console.log(event.type); // => null
    console.log(eventType); // => "click"
  }, 0);
  // Won't work. this.state.clickEvent will only contain null values.
  this.setState({clickEvent: event});
  // You can still export event properties.
  this.setState({eventType: event.type});
}

The EventPluginHub will then process each event by annotating them with "dispatches", a sequence of listeners and IDs that care about that event.

前面也提到了,每个事件都可以有多个处理程序,即使每个处理程序实际上都被真正的 DOM 侦听一次。因此, 需要累积由事件处理程序及其相应的fiber节点(虚拟 DOM 树中的节点)组成的相关 "调度", 以便后面的使用。

The EventPluginHub then dispatches the events.

插件遍历之前累积的信息并调度事件,从而调用提交的事件处理程序。

本文就说了那么多,但是有几点希望你可以知道:

  • 注册到主DOM (window.document)的顶级事件监听器也可以注册到其他DOM,这取决于应用程序容器的位置。比如,容器被iframe采用,那么iframe的DOM将是主要的事件监听器; 它也可以是一个文档片段、一个shadow DOM 等。重要的是,你应该意识到这一点,并知道事件的传播有这小小的限制。

  • React将事件重新调度分为两个阶段: 一个用于捕获,另一个用于冒泡,就像原生DOM那样。

  • 为React Native完成的事件处理与React DOM不同,你不应该混淆这两者! React只是一个库,它生成我们想要呈现的视图的虚拟表示,React DOM/Native是React和我们使用的环境之间的桥梁。(对于这个,可以参考这个地方)。本文仅与React DOM相关!

参考:

Last updated