React 合成事件

503 阅读3分钟

React官方文档:事件处理合成事件

神文: 探索 React 合成事件

一、 概念

合成事件(SyntheticEvent)是浏览器的原生事件的跨浏览器包装器。除兼容所有浏览器外,它还拥有和浏览器原生事件相同的接口,包括 stopPropagation() 和 preventDefault(); 当需要使用浏览器的底层事件时,只需要使用 nativeEvent 属性来获取即可。

// 在React中所有的事件均是合成事件,如下的onClick就属于合成事件,
// 可通过 e.nativeEvent 获取相应的原生DOM事件
const button = <button onClick={(e) => { console.log(e.nativeEvent) }}>button</button>

二、 目的

  1. 进行浏览器兼容,实现更好的跨平台
  2. 避免垃圾回收
  3. 方便事件统一管理和事务机制

需要理解原生DOM事件处理机制与事件委托/代理

顶层事件代理机制: React 将所有监听事件都绑定在document上,能够保证冒泡一致性,兼容跨浏览器执行,利用合成事件模拟并抹平不同浏览器对象之间的差异;

事件对象存在被频繁的创建与回收的情况,因此引入事件池,在事件池中获取与释放对象(即React事件不会被释放,而是被存放进数组中当事件触发时,从对应事件池数组中弹出事件,避免频繁的创建与销毁,即垃圾回收)。

事件注册: 在组件生成的时候将vurtral dom中对应所有的监听事件都注册在document的监听器中,也就是所有的事件处理函数都存放在listenerBank中,并以key作为索引(好处: 将可能要触发的事件分门别类的存放)

统一事件监听,在冒泡阶段处理事件,当挂在或者卸载组件时,只需要在统一的事件监听位置增加或者删除对象,极大的提高效率;事件触发时,组件生成合成事件,传递到document中,document通过dispatch event回调函数分发依次执行dispatch Listener中同类型的事件监听函数;

image.png

三、合成事件与原生DOM事件的区别

  1. 命名不同
  2. 处理函数调用类型不同
  3. 阻止默认行为方式不同
// 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>
    )
}

image.png

  1. 所有的react事件都是合成事件,均被绑定在document元素上
  2. 当真实DOM元素被触发时,会逐步冒泡到顶层元素document上(在冒泡的过程中按照冒泡顺序执行相应的原生DOM监听事件),到达document顶层元素后,按照DOM冒泡触发顺序执行相应的合成事件
  3. 最后执行docuemnt元素自身挂载的监听执行函数