React 事件系统介绍及源码分析

avatar
猫小娱 @猫眼娱乐

举个例子

大家在使用 React 的过程中应该都写过类似这样的代码:

如果 list 有 1000 项怎么办呢?

React 事件系统

几个关键概念

概念 操作 优点
事件委托 几乎将所有事件都委托到 document 减少内存占用和避免频繁的操作DOM
合成事件 对原生 DOM事件对象的封装 所有浏览器中都表现一致,实现了跨浏览器兼容
对象池 利用对象池来管理合成事件对象的创建和销毁 便于统一管理;可以减少垃圾回收和新建对象过程中内存的分配操作,提高了性能
  • 对象池是什么? 对象池其实就是一个集合,里面包含了我们需要的对象集合,这些对象都被对象池所管理,如果需要这样的对象,从池子里取出来就行,但是用完需要归还。

  • 什么时候使用对象池? 初始化、实例化的代价高,且有需求需要经常实例化,但每次实例化的数量又比较少的情况下,使用对象池可以获得显著的效能提升。

大体流程

事件注册事件触发事件清理

源码分析

以开头的例子为示例代码进行分析。

1.事件注册

我们写在 li 元素属性上的事件监听函数最终是怎么绑定到原生 DOM 上的?(以下为部分节选代码

事件注册入口

确定事件最终注册到哪

  • 利用了事件委托,几乎所有的事件最终都会被委托到 document 或者 Fragment 上。

获取原生 DOM 事件名和绑定事件的方式

绑定冒泡阶段的事件监听函数

2.事件触发

点击 li 元素后,发生了什么,事件监听函数是如何被调用的?

事件执行入口

2.1 生成合成事件

从对象池中取出合成事件

  • React 事件系统的一大亮点,它将所有的合成事件都缓存在 对象池 中,可以大大降低对象的创建和销毁的时间,提升性能。

模拟捕获和冒泡

  • 生成合成事件之后,会调用 accumulateTwoPhaseDispatches(event),该方法最终会调用 traverseTwoPhase。

  • 模拟过程中会把所有事件监听函数及其对应的节点都加入到 event(合成事件) 的属性中。

2.2 执行事件

按序执行和清理事件

  • 这也就是为什么当我们在需要异步读取操作一个合成事件对象的时候,需要执行 event.persist(),不然 React 就会在这里释放掉这个事件。

回调函数真正被执行

  • React 在收集回调数组的时候并不会去管我们是否调用了 stopPropagation ,而是会在事件执行的阶段才会去检查是否需要停止冒泡。

3.事件清理 事件执行完之后是如何清理的?

再来举个🌰

1. 当 React 事件和原生事件混用时的阻止冒泡

  • 在 React 事件系统 中调用 e.stopPropagation() 可以阻止 React 的合成事件以及绑定在 window 上的原生事件;

  • 在原生事件 中调用 e.stopPropagation(),如果是在 document 之前,那么所有的 React 事件都会被阻止。

1.点击 button 之后,输出结果是什么?(ABCDE排序)

2.分别把 (1) 和 (2) 的 e.stopPropagation() 加上,输出结果又是什么?(ABCDE排序)

2.异步方式访问事件

有一个模糊搜索框,为了避免频繁的发送请求,利用 setTimeout 加了 200 毫秒延时。