神文: 探索 React 合成事件
一、 概念
合成事件(SyntheticEvent)是浏览器的原生事件的跨浏览器包装器。除兼容所有浏览器外,它还拥有和浏览器原生事件相同的接口,包括 stopPropagation() 和 preventDefault(); 当需要使用浏览器的底层事件时,只需要使用 nativeEvent 属性来获取即可。
// 在React中所有的事件均是合成事件,如下的onClick就属于合成事件,
// 可通过 e.nativeEvent 获取相应的原生DOM事件
const button = <button onClick={(e) => { console.log(e.nativeEvent) }}>button</button>
二、 目的
- 进行浏览器兼容,实现更好的跨平台
- 避免垃圾回收
- 方便事件统一管理和事务机制
顶层事件代理机制: React 将所有监听事件都绑定在document上,能够保证冒泡一致性,兼容跨浏览器执行,利用合成事件模拟并抹平不同浏览器对象之间的差异;
事件对象存在被频繁的创建与回收的情况,因此引入事件池,在事件池中获取与释放对象(即React事件不会被释放,而是被存放进数组中当事件触发时,从对应事件池数组中弹出事件,避免频繁的创建与销毁,即垃圾回收)。
事件注册: 在组件生成的时候将vurtral dom中对应所有的监听事件都注册在document的监听器中,也就是所有的事件处理函数都存放在listenerBank中,并以key作为索引(好处: 将可能要触发的事件分门别类的存放)
统一事件监听,在冒泡阶段处理事件,当挂在或者卸载组件时,只需要在统一的事件监听位置增加或者删除对象,极大的提高效率;事件触发时,组件生成合成事件,传递到document中,document通过dispatch event回调函数分发依次执行dispatch Listener中同类型的事件监听函数;
三、合成事件与原生DOM事件的区别
- 命名不同
- 处理函数调用类型不同
- 阻止默认行为方式不同
// React 合成事件
// 1. 合成事件命名以小驼峰的方式命名,如点击事件onClick
<div onClick={
// 2. 合成事件是以函数形式调用
(e) => {
console.log("合成事件执行函数");
// 3. 阻止默认行为函数执行语句,通过event.preventDefault()
e.preventDefault();
}
}>
content
</div>
<!-- 原生DOM事件 -->
<!-- 1. 命名以全小写方式,如点击事件onclick -->
<!-- 2. 以字符串的形式调用执行 -->
<div onclick="handleClick()">content</div>
<a href="" onclick="console.log("点击a标签行为");return false;">content</a>
<script>
const handleClick = () => {
// 3. 阻止默认行为函数执行语句,通过返回false
return false;
}
</script>
差异总结:
| 差异点 | 原生事件 | React 事件 |
|---|---|---|
| 事件名称命名方式 | 名称全部小写 (onclick, onblur) | 名称采用小驼峰 (onClick, onBlur) |
| 事件处理函数语法 | 字符串 | 函数 |
| 阻止默认行为方式 | 事件返回 false | 使用 e.preventDefault() 方法 |
四、合成事件与原生事件的执行顺序问题
在 React 中,合成事件是以事件委托方式绑定在顶层document上,并在组件卸载(unmount)阶段自动销毁绑定的事件
// 真实DOM元素‘Child’被点击之后的监听事件执行顺序如下console截图
const MyComp = () => {
useEffect(() => {
document.addEventListener("click", () => {
console.log("document click")
})
document.getElementById("father").addEventListener("click", () => {
console.log("原生DOM:father click")
})
document.getElementById("child").addEventListener("click", () => {
console.log("原生DOM:child click")
})
}, [])
return (
<div id="father" onClick={() => { console.log("合成事件:father onClick") }}>
father
<div id="child" onClick={() => { console.log("合成事件:Child onClick") }}>
Child
</div>
</div>
)
}
- 所有的react事件都是合成事件,均被绑定在
document元素上 - 当真实DOM元素被触发时,会逐步冒泡到顶层元素
document上(在冒泡的过程中按照冒泡顺序执行相应的原生DOM监听事件),到达document顶层元素后,按照DOM冒泡触发顺序执行相应的合成事件 - 最后执行
docuemnt元素自身挂载的监听执行函数