初窥门径:React中的事件机制

React中的事件机制

什么是合成事件?

在React中,合成事件(Synthetic Events) 是一种跨浏览器的事件包装机制 ,旨在统一浏览器的事件处理方式,解决跨浏览器兼容性问题,并提供更高效、更一致的事件处理体验。React 的合成事件系统是在 React 的事件系统基础上构建的,它为 DOM 事件提供了一个标准化的接口,使得事件处理程序不需要考虑不同浏览器之间的差异。简单来说,它是对浏览器原生事件的一个封装。

合成事件对象是 SyntheticEvent 的实例 ,具有与原生事件相同的方法和属性,如 preventDefault(), stopPropagation() 等。如果想要获得原⽣DOM事件对象,可以通过 e.nativeEvent 属性获取。

js 复制代码
// 原生事件回调处理,分别在回调函数中打印事件对象,原生事件和合成事件对象如下图所示
<button onclick="handleClick()">原生事件</button>
// react中合成事件回调处理
<button onClick={handleClick}>react合成事件</button>

使用合成事件的好处

  • 跨浏览器一致性: 原生 DOM 事件在不同的浏览器中有些行为差异(如事件的触发方式、事件对象的属性等)。React 的合成事件系统统一了这些差异,使得开发者可以编写兼容所有浏览器的事件处理代码。
  • 性能优化: React 通过事件委托的方式,将事件处理绑定到root元素上,事件冒泡时,React 会通过事件池重用合成事件对象,从而避免频繁创建和销毁事件对象,减少性能开销。
  • 事件池(Event Pooling): React 使用事件池技术来重用事件对象。在事件处理程序被调用后,合成事件对象会被放回事件池,防止频繁创建和销毁事件对象,从而提高性能。
  • 简化的 API: React 的合成事件为开发者提供了一致、简单的 API,使得事件的处理更加直观。

事件委托

在 React 组件中,通常会内联编写事件处理。但是,对大多数事件来说,React 实际上并不会将它们附加到 DOM 节点上。相反,React会直接在 document 节点上(在 React 17 中,React 将不再向 document 附加事件处理器。而会将事件处理器附加到渲染 React 树的根 DOM 容器中 )为每种事件类型附加一个处理器。这被称为事件委托。

当 document 上触发 DOM 事件时,React 会找出调用的组件,然后 React 事件会在组件中向上 "冒泡"。但实际上,原生事件已经冒泡出了 document 级别,React 在其中安装了事件处理器。

js 复制代码
import React, { useEffect} from 'react'
export default function App() {
  useEffect(() => {
    const parent = document.getElementById('react')
    parent.addEventListener('click', e =>  console.log('addEventListener click'))
  }, [])
  return (
    <div id="react" style={{width: '100px', height: '100px', backgroundColor: 'red'}}
      onClick={(e) => console.log('react onClick')}>
    </div>
  )
}

如下图,通过Event Listeners选项卡不难发现,React内部将onClick事件确实代理到了root 上,而原生addEventListener添加的事件则是绑定在child元素上。

虽然 onClick 看似绑定到 DOM 元素上,但实际上并不会把事件代理函数直接绑定到真实的节点上,而是把所有的事件绑定到root节点上,使用⼀个统⼀的事件去监听这个事件监听器上维持了⼀个映射来保存所有组件内部的事件监听和处理函数。当组件挂载或卸载时,只是在这个统⼀的事件监听器上插⼊或删除⼀些对象。当事件发⽣时,首先被这个统⼀的事件监听器处理,然后在映射⾥找到真正的事件处理函数并调⽤。这样做简化了事件处理和回收机制,效率也有很大提升。

事件池

SyntheticEvent 对象会被放入池中统一管理。React 为了提高性能,会重用事件对象。当事件处理函数执行完后,合成事件会被放入事件池中,并且会在事件处理完成后将这些对象清空 。因此,如果需要异步访问事件对象的属性或方法,需要调用 event.persist() 来避免被回收。

js 复制代码
function handleClick(event) {
  event.persist(); // 阻止事件池回收
  setTimeout(() => {
    console.log(event.target); // 异步访问事件对象时,需要调用 persist
  }, 1000);
}

从 v17 开始,e.persist() 将不再生效,因为 SyntheticEvent 不再放入事件池中。

React事件执行顺序

js 复制代码
import React, { useEffect, useRef } from 'react'
import './index.css'
export default function App() {
  const parentRef = useRef(null)
  const childRef = useRef(null)
  useEffect(() => {
    const root = document.querySelector('#root')
    root.addEventListener('click', e => {
      console.log('原生事件: root DOM监听')
    })
    parentRef.current.addEventListener('click', e => {
      console.log('原生事件: parent DOM监听')
    })
    childRef.current.addEventListener('click', e => {
      console.log('原生事件: child DOM监听')
      // e.stopPropagation() // 阻止原生事件冒泡
    })
  }, [])
  return (
    <div>
      <div id="parent" ref={parentRef} onClick={(e) => {
        console.log('React事件:parent click')
        // e.nativeEvent.stopImmediatePropagation() // 合成事件冒泡完成后阻止执行root节点的原生事件
      }}>
        <div id="child" ref={childRef} onClick={(e) => {
          console.log('React事件:child click')
          // e.stopPropagation() // 阻止合成事件冒泡
        }}></div>
      </div>
    </div>
  )
}

可以发现,当点击子元素时,事件执行顺序如下图:

点击父元素时,事件执行顺序如下图:

因此可以得出结论,React中的合成事件和原生事件执行顺序 是:

1、当真实 DOM 元素触发事件时会先执行该元素上的原生事件,并冒泡以及执行冒泡到的元素上的原生事件;

2、当冒泡到root节点时,会进行一个派发 操作dispatchEvent以执行触发事件的目标元素上的React合成事件,并逐渐冒泡并执行这些元素上的React合成事件(如果有的话);

3、当冒泡到root节点时,最后执行root节点上的原生事件。

参考:React合成事件------老版文档.

相关推荐
破浪前行·吴5 分钟前
【初体验】【学习】Web Component
前端·javascript·css·学习·html
染指悲剧2 小时前
vue实现虚拟列表滚动
前端·javascript·vue.js
浩浩测试一下3 小时前
Web渗透测试之XSS跨站脚本之JS输出 以及 什么是闭合标签 一篇文章给你说明白
前端·javascript·安全·web安全·网络安全·html·系统安全
前端搬运工X4 小时前
Object.keys 的原生 JS 类型之困
javascript·typescript
肖老师xy5 小时前
h5使用better scroll实现左右列表联动
前端·javascript·html
一路向北North5 小时前
关于easyui select多选下拉框重置后多余显示了逗号
前端·javascript·easyui
Libby博仙5 小时前
.net core 为什么使用 null!
javascript·c#·asp.net·.netcore
一水鉴天5 小时前
为AI聊天工具添加一个知识系统 之26 资源存储库和资源管理器
前端·javascript·easyui
万物得其道者成5 小时前
在高德地图上加载3DTilesLayer图层模型/天地瓦片
前端·javascript·3d
你挚爱的强哥6 小时前
基于element UI el-dropdown打造表格操作列的“更多⌵”上下文关联菜单
javascript·vue.js·elementui