可能是你见过最好理解的 React17 合成事件 源码解读

背景

React SyntheticEvent的出现原因,大致上有三点:

1、跨平台

React 出现的意义之一就是跨平台,抹平不同浏览器之间的差异

这一点上 SyntheticEvent 是作为 React 的具体实现而诞生的

2、性能优化

如果在每个元素上有绑定事件,那么会带来大量的性能消耗

原生 JavaScript 的实现中有「事件委托」的优化方式

SyntheticEvent 的底层原理也是它,是对原生 DOM 事件上的一层封装

3、内部调度优化

SyntheticEvent 按照事件的紧急程度,把事件分为多个优先级,避免低优先级的事件阻塞渲染或者影响用户体验

DiscreteEvent (离散事件)、UserBlockingEvent(用户阻塞事件)、ContinuousEvent(连续事件)


如何找到源码调试的切入口?

我喜欢的调试方式是去启动项目,开performance跑一下
在这张图中,可以找到合成事件初始化的入口 listenToAllSupportedEvents
在这张图中,可以找到合成事件触发的入口 dispatchDiscreteEvent


具体实现

以下代码来源于React-DOM V17.0.2

关键代码的注释写在图片里的代码上一行

注册事件

listenToAllSupportedEvents

这一步是对所有原生的事件做一个遍历去执行注册监听的动作

如下图,所有的事件最终都会在root节点上注册监听

nonDelegated 的事件,无法冒泡委托,就只注册了捕获的事件,如scroll

listenToNativeEvent

addTrappedEventListener

passive可以参考官方文档

这一步最主要的事就是获取了带优先级的事件监听器,比如onClick就是调用的是dispatchDiscreteEvent

createEventListenerWrapperWithPriority

根据事件类型,在eventPriorities中定义了事件的优先级 各类事件对应的优先级如下:

addEventBubbleListener / addEventCaptureListener

这一步就是真正挂载事件了

事件收集 & 触发

以click事件为例

事件的触发函数从dispatchDiscreteEvent开始,最核心的调用点就是这个dispatchEventsForPlugins

这中间的函数做了一些辅助操作,如:根据优先级调度、获取事件的关键属性...

dispatchEventsForPlugins

extractEvents$4

合成事件映射关系:

合成事件对象原型链上的preventDefaultstopPropagation(这两个方法都针对IE做了兼容):

accumulateSinglePhaseListeners

这一步可以理解为从底往上的DFS

processDispatchQueue

processDispatchQueueItemsInOrder

为了模拟真实事件触发的顺序

捕获是倒序 执行,冒泡是正序执行

错误处理

当我们在浏览器中执行 JavaScript 代码时,如果发生了错误,浏览器会触发一个 error 事件,并将错误对象作为参数传递给事件处理器。我们可以通过 window.addEventListener('error', handler) 来监听这个事件,并在 handler 函数中处理错误。

但是,如果我们直接执行回调函数,而不是通过事件机制,那么错误就不会被 error 事件捕获,而是直接抛出到全局作用域,可能导致整个应用崩溃。例如:

js 复制代码
function callback() {
  throw new Error('Something went wrong');
}

// 直接执行回调函数,错误不会被捕获
callback();

为了避免这种情况,React 使用了一个巧妙的技巧,就是通过一个 fakeNode 和一个自定义的事件来模拟事件机制,从而实现一个安全的调用回调函数的方法


FAQ

1、在 React17 之后,每一个 fiber 元素上都会绑定具体的捕获/冒泡事件吗

基本不会,但是在onClick的场景下会有一个额外的事件

trapClickOnNonInteractiveElement

这段代码是React合成事件系统的一部分,它用于解决移动Safari浏览器上的一个bug

这个bug导致非交互元素(如div或span)上的点击事件不会正确地冒泡,从而无法触发委托的点击监听器。

为了解决这个问题,这段代码给目标节点添加了一个空的点击监听器,这样就可以让点击事件正常地冒泡。

这个监听器是通过onclick属性设置的,而不是通过addEventListener方法,这样就不需要额外的管理工作。

这段代码还有一个TODO注释,表示可能只需要针对相关的Safari浏览器做这个处理,而不是所有的浏览器。

相关推荐
Dcc43 分钟前
构建可维护的 React 应用:系统化思考 State 的分类与管理
前端·react.js
进阶的鱼1 小时前
React+ts+vite脚手架搭建(五)【规范篇】
前端·react.js·vite
Gazer_S2 小时前
【理解React Hooks与JavaScript类型系统】
前端·javascript·react.js
.生产的驴3 小时前
React useEffect组件渲染执行操作 组件生命周期 监视器 副作用
前端·css·react.js·ajax·前端框架·jquery·html5
蓝婴天使16 小时前
基于 React + Go + PostgreSQL + Redis 的管理系统开发框架
react.js·postgresql·golang
知识分享小能手17 小时前
微信小程序入门学习教程,从入门到精通,项目实战:美妆商城小程序 —— 知识点详解与案例代码 (18)
前端·学习·react.js·微信小程序·小程序·vue·前端技术
DoraBigHead17 小时前
React 中的代数效应:从概念到 Fiber 架构的落地
前端·javascript·react.js
今天头发还在吗18 小时前
【框架演进】Vue与React的跨越性变革:从Vue2到Vue3,从Class到Hooks
javascript·vue.js·react.js
需要兼职养活自己1 天前
react【portals】与vue3【<Teleport>】
前端·react.js
用户47949283569151 天前
React 19.2 重磅更新:终于解决 useEffect 依赖数组难题
前端·react.js