1. 首页

React源码解析(四):事件系统

笔者将编写”React 源码解析”系列文章三到四篇,阐述 React 内部的机制。欢迎大家关注我的掘金账号,以便能及时看到最新的文章更新推送。

在前面三篇文章中,我们阐述了 react 组件的构成与生命周期,setState 的机制。这次我们来谈谈 React 的事件处理。

1.原生事件系统

我们通常监听真实 DOM。举 🌰 来说,我们想监听按钮的点击事件,那么我们在按钮 DOM 上绑定事件和对应的回调函数即可。 遗憾的是若页面复杂且事件处理频率高,那么对网页性能是个考验。

2.React 事件系统

react 的事件处理再眼花缭乱终究还是要回归原生的事件系统,但它做的封装却很优雅。我们直接上结论:

  • React 实现了 SyntheticEvent 层处理事件

什么意思呢?详细来说,React 并不像原生事件一样将事件和 DOM 一一对应,而是将所有的事件都绑定在网页的 document,通过统一的事件监听器处理并分发,找到对应的回调函数并执行。按照官方文档的说法,事件处理程序将传递 SyntheticEvent 的实例,那么接下来我们一探 SyntheticEvent 的究竟。

3.SyntheticEvent

1.事件注册

上文说到,既然 React 对事件统一进行处理,那么肯定需要先注册程序员写的事件触发函数吧?那么这个过程是在哪里执行的呢?因为我们是把事件”绑定”在”组件 DOM”上,例如一个点击事件:

<Component onClick={this.handleClick} />

其实在这个组件挂载的时候,React 就已经开始通过mountCompoent内部的_updateDOMProperties方法进行事件处理了。在这个方法中,执行的是enqueuePutListener方法去注册事件:

React源码解析(四):事件系统

顺藤摸瓜,listenTo方法关键调用了以下两个函数:

  • trapBubbledEvent
  • trapCapturedEvent

熟悉原生事件系统的读者从英文翻译就能知道,两个函数是用来处理事件捕获和事件冒泡的。具体处理逻辑不分析,我们直接看这两个函数内部:

React源码解析(四):事件系统

上述代码中的target也就是document,也看到了熟悉的document.addEventListenerdocument.removeEventListener。正是这样统一的事件绑定减少了内存的开销。

2.事件存储

我们写的事件回调函数注册完毕后需要存储起来,以便触发时进行回调。存储的入口是EventPluginHub.putListener函数:

React源码解析(四):事件系统

可见所有的回调函数都以二维数组的形式存储在listenerBank中,根据组件对应的key来进行管理。

3.事件分发

事件注册和事件存储我们已经清楚了,现在我们看下当事件触发时,React 是如何进行事件分发和找到对应回调函数并执行的。分发入口在ReactDOMEventListener.jshandleTopLevelImpl:

React源码解析(四):事件系统

上述代码我们理清了流程:因为事件回调函数执行后可能导致 DOM 结构的变化,那么 React 先将当前的结构以数组的形式存储起来,依次遍历执行。 上述函数的_handleTopLevel最终对回调函数进行处理,看下源码:

React源码解析(四):事件系统

代码中出现了新角色:EventPluginHub.extractEvents。查阅相关资料,得知extractEvents方法是用于合成事件的,也就是根据事件类型的不同,合成不同的跨浏览器的SyntheticEvent对象的实例,比如SyntheticClickEvent。而EventPluginHub顾名思义是 React 进行合成事件时所用的工具插件:

React源码解析(四):事件系统

可以看到对于不同的事件,React 将使用不同的功能插件,这些插件都是通过依赖注入的方式进入内部使用的。React 合成事件的过程非常繁琐,但可以概括出extractEvents函数内部主要是通过switch函数区分事件类型并调用不同的插件进行处理从而生成SyntheticEvent实例。有兴趣的同学可以自行了解。

4.事件处理

Js 中文网 – 前端进阶资源教程 https://www.javascriptC.com/,typescript 中文手册
专注分享前端知识,你想要的,在这里都能找到

React 处理事件的思想与处理setState的思想类似,都是采用批处理的方法。在上面handleTopLevel方法中我们看到最后执行了runEventQueueInBatch方法:

//事件进入队列
EventPluginHub.enqueueEvents(events);
//...
EventPluginHub.processEventQueue(false);

看下processEventQueue

React源码解析(四):事件系统

上述代码遍历队列中的事件,并进入executeDispatchesAndReleaseSimulated

event.constructor.release(event);

Js 中文网 – 前端进阶资源教程 https://www.javascriptC.com/,typescript 中文手册
专注分享前端知识,你想要的,在这里都能找到

这行代码将 React 的合成事件 release 掉,减少内存开销。事件处理的核心入口在executeDispatchesInOrder:

var dispatchListeners = event._dispatchListeners;
var dispatchInstances = event._dispatchInstances;

executeDispatch(event, simulated, dispatchListeners[i], dispatchInstances[i]);

重要的代码就这三行,dispatchListeners是事件回调函数,dispatchInstances是对应的组件,将这些参数传入executeDispatch后:

function executeDispatch(event, simulated, listener, inst) {
  var type = event.type || 'unknown-event';
  ReactErrorUtils.invokeGuardedCallback(type, listener, event);
}

invokeGuardedCallback就相当简单了:

function invokeGuardedCallback(name, func, a) {
  func(a);
}

Js 中文网 – 前端进阶资源教程 https://www.javascriptC.com/,typescript 中文手册
专注分享前端知识,你想要的,在这里都能找到

上面的func(a)其实就是listener(event),再往上追溯,就是dispatchListeners(dispatchInstances),这也就说明为什么我们的 React 事件回调函数可以拿到原生的事件了。

4.总结

React 事件系统为了兼容各种版本的浏览器而做了大量工作,我们不必钻牛角尖去研究这些是如何实现的,与原生事件不同的点,只在于 React 对事件进行统一而不是分散的存储与管理,捕获事件后内部生成合成事件提高浏览器的兼容度,执行回调函数后再进行销毁释放内存,从而大大提高网页的响应性能。

回顾:

联系邮箱:ssssyoki@foxmail.com

作者:ssssyoki
链接:https://www.javascriptc.com/3954.html

看完两件小事

如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:

  1. 关注我们的 GitHub 博客,让我们成为长期关系
  2. 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
  3. 关注公众号 「画漫画的程序员」,公众号后台回复「资源」 免费领取我精心整理的前端进阶资源教程

JS中文网是中国领先的新一代开发者社区和专业的技术媒体,一个帮助开发者成长的社区,目前已经覆盖和服务了超过 300 万开发者,你每天都可以在这里找到技术世界的头条内容。欢迎热爱技术的你一起加入交流与学习,JS中文网的使命是帮助开发者用代码改变世界

本文著作权归作者所有,如若转载,请注明出处

转载请注明:文章转载自「 Js中文网 · 前端进阶资源教程 」https://www.javascriptc.com

标题:React源码解析(四):事件系统

链接:https://www.javascriptc.com/3954.html

« 谷歌首席软件工程师:如何设计大型应用程序
React Hook快速入门»
Flutter 中文教程资源

相关推荐

QR code