react的合成事件
如果各位看了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
*/
下面就来说说这个。如果图片更直接,那么就来看看这个图片:
react event
可以发现,和上面注释里没啥区别。
来一步步的解析,让这货更明朗起来。
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相关!