React 事件机制大揭秘:从 addEventListener 到合成事件,别再一脸懵逼!

大家好,我是一名正在努力学习前端知识的同学。 今天这篇,咱们来把 React 事件机制 按头梳理一遍,看完直接拿去面试、写简历、嘴硬吵架都行,保证你能把「合成事件」三个字说得理直气壮。


先来点前菜:JS 原生事件机制

DOM0、DOM1、DOM2 听过没?

  • DOM0 级事件

    远古写法,HTML 里写 onclick,比如:

    ini 复制代码
    <a onclick="doSomething()"></a>

    缺点也明显:一个元素一个事件,一个 overwrite 掉另一个,灵活度为 0。

  • DOM1

    基本没对事件机制做扩展,就是个模型过渡期,存在感很低。

  • DOM2 级事件

    终于正规化了 addEventListener

    bash 复制代码
    element.addEventListener(type, listener, useCapture);
    • type: 事件类型,比如 'click'
    • listener: 你的回调函数
    • useCapture: 布尔值,默认 false,表示在冒泡阶段执行;true 则在捕获阶段执行。

前端同学第一次用 addEventListener,都是从 DOM2 级开始的。


事件是怎么传递的?搞清捕获和冒泡

很多人听过冒泡,但捕获是啥?其实浏览器执行顺序是:

1️⃣ 从 document 开始往目标节点走,这叫 捕获阶段

2️⃣ 到达目标节点,这里叫 目标阶段

3️⃣ 再从目标节点往上走回 document,这叫 冒泡阶段

默认 addEventListener 是在 冒泡阶段 执行的,所以大多数事件处理是在冒泡阶段搞定的。


一句话总结:JS 事件机制是个异步大工程

JS 的回调、Promise、async/await 都是异步执行,你的事件监听器也是一样,浏览器把点击放进队列,等主线程空了再处理。

事件委托:大型项目离不开的优化利器

🥷什么是事件委托?

简单说,就是不把事件绑在每个小元素身上,而是绑在它们的公共父级(甚至直接绑在 document)上。

然后利用 event.target 判断点的是谁,谁点谁负责。

为啥要事件委托?三个字:省资源

1. 性能最优

想象一个页面有 5000 个按钮,如果每个按钮都 addEventListener,浏览器笑得合不拢嘴(指 CPU 飙升)。

用事件委托,只需要给外层包裹元素绑一个监听器,就能动态处理所有子元素的点击事件。

javascript 复制代码
document.querySelector('#app').addEventListener('click', (e) => {
  if (e.target.matches('.btn')) {
    // 谁点了谁触发
  }
});

2. 动态节点也不怕

滚动加载、无限下拉?一页动态插入 100 条评论?

用事件委托根本不怕!因为新节点插进去后,不需要重新注册事件,老监听器还在,event.target 自己会找到它。

3. 避免重复注册

很多时候,搞着搞着就对同一个元素绑了同一个事件多次,页面一操作结果多次触发,bug 还神秘难找。事件委托可以从源头避免这事。


再说说两个「阻止」大法

event.preventDefault()

防止默认行为,比如:

  • 表单提交:form 默认会刷新页面。
  • <a href="#"> 点击后默认会跳转或回到顶部。

event.stopPropagation()

防止事件冒泡,有啥用?

举个栗子,做个弹窗:

  • 弹窗外层 document 上有个点击监听,点哪里都关闭弹窗。
  • 弹窗内部按钮也有点击事件,点按钮不能关闭弹窗。

这时就得在内部按钮点击时 stopPropagation,否则一点击按钮,冒泡到外层就把弹窗关了。


React 的合成事件:其实是个高级事件代理

⚙️ React 怎么处理事件?

React 不会真的在每个元素上绑监听器,而是用了一套 事件代理 机制:

  • 所有事件都挂在根节点 #root 上,咱们的 <div> <button> 并没有真正绑定原生事件。
  • 发生事件时,React 先收集,再在内部处理。
  • 这玩意叫 SyntheticEvent(合成事件)

事件池(Event Pooling)

老版本 React(17 以前)里为了节省内存,每次事件执行完后会把事件对象里的属性清空回收,下次复用。

所以以前咱们经常看见 e.persist(),就是告诉 React:别清空这个事件对象,我还要用。

好消息是:React 17+ 以后,这个事件池基本安全了,大多数时候可以放心使用事件对象,不需要 persist


总结:前端事件三板斧

1️⃣ 理解 捕获阶段冒泡阶段目标阶段

2️⃣ 合理用好 事件委托 ,性能翻倍不是梦

3️⃣ 面对 React 的 合成事件,别慌,它就是事件代理 + 池化,React 帮你兜底了。


最后,彩蛋来了

面试官:React 的事件是怎么实现的?

你:事件委托挂在 #root,内部用 SyntheticEvent,事件池可回收,低开销高性能。

面试官:过!


好了,这就是今天的分享,如果觉得有用,麻烦点个 收藏一下,我不做代码混子,就做码界段子手,我们下篇见!

相关推荐
gnip10 分钟前
SSE技术介绍
前端·javascript
yinke小琪25 分钟前
JavaScript DOM节点操作(增删改)常用方法
前端·javascript
枣把儿29 分钟前
Vercel 收购 NuxtLabs!Nuxt UI Pro 即将免费!
前端·vue.js·nuxt.js
望获linux30 分钟前
【Linux基础知识系列】第四十三篇 - 基础正则表达式与 grep/sed
linux·运维·服务器·开发语言·前端·操作系统·嵌入式软件
爱编程的喵31 分钟前
从XMLHttpRequest到Fetch:前端异步请求的演进之路
前端·javascript
喜欢吃豆34 分钟前
深入企业内部的MCP知识(三):FastMCP工具转换(Tool Transformation)全解析:从适配到增强的工具进化指南
java·前端·人工智能·大模型·github·mcp
豆苗学前端37 分钟前
手把手实现支持百万级数据量、高可用和可扩展性的穿梭框组件
前端·javascript·面试
不见_37 分钟前
不想再写周报了?来看看这个吧!
前端·命令行
yinke小琪39 分钟前
JavaScript 事件冒泡与事件捕获
前端·javascript
pany41 分钟前
写代码的节奏,正在被 AI 改写
前端·人工智能·aigc