💕 温馨提示: 下边是对React合成事件的源码阅读,全文有点长,但是!如果你真的想知道这不为人知的背后内幕,那一定要耐心看下去!
最近在做一个功能,然后不小心
踩到了 React 合成事件 的坑,好奇心的驱使,去看了 React 官网合成事件 的解释,这不看不知道,一看吓一跳…
SyntheticEvent是个什么鬼?咋冒出来了个事件池?
我就一个简单的需求功能,为什么能扯出这些鬼玩意??
我们先简单的来看一看我的需求功能是个啥???
需要做一个弹窗打开/关闭
的功能,当点击 button
的时候打开,此时打开的情况下,点击弹窗 区域
外,就需要关闭。
这简单嘛,直接在 button
上注册一个点击事件,同时在 document.body
注册一个点击事件,然后在 弹窗container
里阻止冒泡,很难嘛?
class FuckEvent extends React.PureComponent {
state = {
showBox: false
}
componentDidMount() {
document.body.addEventListener('click', this.handleClickBody, false)
}
componentWillUnmount() {
document.body.removeEventListener('click', this.handleClickBody, false)
}
handleClickBody = () => {
this.setState({
showBox: false
})
}
handleClickButton = () => {
this.setState({
showBox: true
})
}
render() {
return (
<div>
<button onClick={
this.handleClickButton}>点击我显示弹窗</button>
{
this.state.showBox && ( <div onClick={
e => e.stopPropagation()}>我是弹窗</div>
)} </div>
)
}
}
很简单嘛,很开心的点击了弹窗区域…
于是…我没了…点击弹窗区域,弹窗也被关闭了。。。what the f**k ??? 难道冒泡没有用 ?
带着这个问题,我走上了不归之路
…
我们都知道,什么是事件委托,(不知道的出门左拐 👈) 在前端刀耕火种时期,事件委托可是爸爸
事件委托解决了庞大的数据列表时,无需为每个列表项绑定事件监听。同时可以动态挂载元素无需作额外的事件监听处理。
你看,事件委托那么牛 13,你觉得 React 会不用?呵,React 不仅用了,还用的非常溜 ~
怎么说呢,react 它接管了浏览器事件的优化策略,然后自身实现了一套自己的事件机制,而且特别贴心,就跟你男朋友一样,它把浏览器的不同差异,都帮你消除了 ~
React 实现了一个合成事件层,就是这个事件层,把 IE 和 W3C 标准之间的兼容问题给消除了。
📌 那么问题来了,什么是合成事件与原生事件???
原生事件: 在 componentDidMount生命周期
里边进行addEventListener
绑定的事件
合成事件: 通过 JSX 方式绑定的事件,比如 onClick={() => this.handle()}
还记得上边的那个例子吗?我们在弹窗的 DOM 元素上绑定了一个事件,进行阻止冒泡
相关参考视频讲解:进入学习
{
this.state.showBox && <div onClick={
e => e.stopPropagation()}>我是弹窗</div>
}
然后在componentDidMount生命周期
里边对 body 进行了 click 的绑定
componentDidMount() {
document.body.addEventListener('click', this.handleClickBody, false)
}
componentWillUnmount() {
document.body.removeEventListener('click', this.handleClickBody, false)
}
我们去分析一下,因为合成事件的触发是基于浏览器的事件机制来实现的,通过冒泡机制冒泡到最顶层元素,然后再由 dispatchEvent 统一去处理
回顾一下浏览器事件机制
Document 上边是 Window,这里截的是《JavaScript 高级程序设计》书籍里的图片
浏览器事件的执行需要经过三个阶段,捕获阶段-目标元素阶段-冒泡阶段。
🙋 Question: 此时对于合成事件进行阻止,原生事件会执行吗?答案是: 会!
📢 Answer: 因为原生事件先于合成事件执行 (个人理解: 注册的原生事件已经执行,而合成事件处于目标阶段,它阻止的冒泡只是阻止合成的事件冒泡,但是原生事件在捕获阶段就已经执行了)
React 自己实现了这么一套事件机制,它在 DOM 事件体系基础上做了改进,减少了内存的消耗,并且最大程度上解决了 IE 等浏览器的不兼容问题
那它有什么特点?
React 上注册的事件最终会绑定在document
这个 DOM 上,而不是 React 组件对应的 DOM(减少内存开销就是因为所有的事件都绑定在 document 上,其他节点没有绑定事件)
React 自身实现了一套事件冒泡机制,所以这也就是为什么我们 event.stopPropagation()
无效的原因。
React 通过队列的形式,从触发的组件向父组件回溯,然后调用他们 JSX 中定义的 callback
React 有一套自己的合成事件 SyntheticEvent
,不是原生的,这个可以自己去看官网
React 通过对象池的形式管理合成事件对象的创建和销毁,减少了垃圾的生成和新对象内存的分配,提高了性能
看到这里,应该对 React 合成事件有一个简单的了解了吧,我们接着去看一看源码 ~
👉 源码 ReactBrowserEventEmitter
我们在 ReactBrowserEventEmitter.js
文件中可以看到,React 合成系统框架图
/**
* React和事件系统概述:
*
* +------------+ .
* | DOM | .
* +------------+ .
* | .
* v .
* +------------+ .
* | ReactEvent | .
* | Listener | .
* +------------+ . +-----------+
* | . +--------+|SimpleEvent|
* | . | |Plugin |
* +-----|------+ . v +-----------+
* | | | . +--------------+ +------------+
* | +-----------.--->|EventPluginHub| | Event |
* | | . | | +-----------+ | Propagators|
* | ReactEvent | . | | |TapEvent | |------------|
* | Emitter | . | |<---+|Plugin | |other plugin|
* | | . | | +-----------+ | utilities |
* | +-----------.--->| | +------------+
* | | | . +--------------+
* +-----|------+ . ^ +-----------+
* | . | |Enter/Leave|
* + . +-------+|Plugin |
* +-------------+ . +-----------+
* | application | .
* |-------------| .
* | | .
* | | .
* +-------------+ .
* .
*/
源码里边的一大串英文解释,我帮你们 google 翻译了,简单来讲就是:
Top-level delegation 用于捕获最原始的浏览器事件,它主要由 ReactEventListener 负责,ReactEventListener 被注入后可以支持插件化的事件源,这一过程发生在主线程。
React 对事件进行规范化和重复数据删除,以解决浏览器的怪癖。这可以在工作线程中完成。
将这些本地事件(具有关联的顶级类型用来捕获它)转发到EventPluginHub
,后者将询问插件是否要提取任何合成事件。
然后,EventPluginHub 将通过为每个事件添加“dispatches”(关心该事件的侦听器和 ID 的序列)来对其进行注释来进行处理。
再接着,EventPluginHub 会调度分派事件.
❗ 建议直接去看英文注释,翻译可能不是很标准。
看会上边的框架图,我们得先知道一下这些都是个啥玩意,直接看名称,也能