React事件机制 - 源码概览(上)

某次被问到 React事件机制的问题,关于这一块我确实不怎么清楚,因为平时大部分工作都是用 Vue,对于 React的熟悉程度只限于会用,具体实现逻辑还真没专门学习过,但是总不能就说自己不清楚吧,好在我了解 Vue的事件机制,于是就把 Vue的事件机制说了一遍,最后再来一句“我觉得 React应该和 Vue的差不多”后来我想了下应该没那么简单,于是网上搜了下相关文章,发现果然是被我想得太简单了,...
摘要由CSDN通过智能技术生成

某次被问到 React事件机制的问题,关于这一块我确实不怎么清楚,因为平时大部分工作都是用 Vue,对于 React的熟悉程度只限于会用,具体实现逻辑还真没专门学习过,但是总不能就说自己不清楚吧,好在我了解 Vue的事件机制,于是就把 Vue的事件机制说了一遍,最后再来一句“我觉得 React应该和 Vue的差不多”

后来我想了下应该没那么简单,于是网上搜了下相关文章,发现果然是被我想得太简单了,Vue通过编译模板,解析出事件指令,将事件和事件回调附加到 vnode tree上,在 patch过程中的创建阶段和更新阶段都会对这个 vnode tree进行处理,拿到每个 vnode上附加的事件信息,就可以调用原生 DOM API对相应事件进行注册或移除,流程还是比较清晰的,而React则是单独实现了一套事件机制

本文以 React v16.5.2 为基础进行源码分析

基本流程

react源码的 react-dom/src/events/ReactBrowserEventEmitter.js文件的开头,有这么一大段注释:

/**
 * Summary of `ReactBrowserEventEmitter` event handling:
 *
 *  - Top-level delegation is used to ......
 * ......
 *
 * +------------+    .
 * |    DOM     |    .
 * +------------+    .
 *       |           .
 *       v           .
 * +------------+    .
 * | ReactEvent |    .
 * |  Listener  |    .
 * +------------+    .                         +-----------+
 *       |           .               +--------+|SimpleEvent|
 *       |           .               |         |Plugin     |
 * +-----|------+    .               v         +-----------+
 * |     |      |    .    +--------------+                    +------------+
 * |     +-----------.--->|EventPluginHub|                    |    Event   |
 * |            |    .    |              |     +-----------+  | Propagators|
 * | ReactEvent |    .    |              |     |TapEvent   |  |------------|
 * |  Emitter   |    .    |              |<---+|Plugin     |  |other plugin|
 * |            |    .    |              |     +-----------+  |  utilities |
 * |     +-----------.--->|              |                    +------------+
 * |     |      |    .    +--------------+
 * +-----|------+    .                ^        +-----------+
 *       |           .                |        |Enter/Leave|
 *       +           .                +-------+|Plugin     |
 * +-------------+   .                         +-----------+
 * | application |   .
 * |-------------|   .
 * |             |   .
 * |             |   .
 * +-------------+   .
 *                   .
 *    React Core     .  General Purpose Event Plugin System
 */

这段注释第一段文本内容被我省略掉了,其主要是在大概描述 React的事件机制,也就是这个文件中的代码要做的一些事情,大概意思就是说事件委托是很常用的一种浏览器事件优化策略,于是 React就接管了这件事情,并且还贴心地消除了浏览器间的差异,赋予开发者跨浏览器的开发体验,主要是使用 EventPluginHub这个东西来负责调度事件的存储,合成事件并以对象池的方式实现创建和销毁,至于下面的结构图形,则是对事件机制的一个图形化描述

根据这段注释,大概可以提炼出以下几点内容:

  • React事件使用了事件委托的机制,一般事件委托的作用都是为了减少页面的注册事件数量,减少内存开销,优化浏览器性能,React这么做也是有这么一个目的,除此之外,也是为了能够更好的管理事件,实际上,React中所有的事件最后都是被委托到了 document这个顶级DOM
  • 既然所有的事件都被委托到了 document上,那么肯定有一套管理机制,所有的事件都是以一种先进先出的队列方式进行触发与回调
  • 既然都已经接管事件了,那么不对事件做些额外的事情未免有些浪费,于是 React中就存在了自己的 合成事件(SyntheticEvent),合成事件由对应的 EventPlugin负责合成,不同类型的事件由不同的 plugin合成,例如 SimpleEvent PluginTapEvent Plugin
  • 为了进一步提升事件的性能,使用了 EventPluginHub这个东西来负责合成事件对象的创建和销毁

下文均以下述这段代码为示例进行分析:

export default class MyBox extends React.Component {
   
  clickHandler(e) {
   
    console.log('click callback', e)
  }
  render() {
   
    return (
      <div className="box" onClick={
   this.clickHandler}>文本内容</div>
    )
  }
}

事件注册

只看相关主体流程,其他诸如 vnode的创建等前提流程就不管了,从setInitialDOMProperties这个方法开始看起,这个方法主要用于遍历 ReactNodeprops对象,给最后将要真正渲染的真实 DOM对象设置一系列的属性,例如 styleclassautoFocus,也包括innerHTMLevent的处理等,示例中 .box元素的 props对象结构如下:

在这里插入图片描述

这个方法中有个 case,就是专门用于处理事件的:

// react-dom/src/client/ReactDOMComponent.js
else if (registrationNameModules.hasOwnProperty(propKey)) {
   
  if (nextProp != null) {
   
    if (true && typeof nextProp !== 'function') {
   
      warnForInvalidEventListener(propKey, nextProp);
    }
    // 处理事件类型的 props
    ensureListeningTo(rootContainerElement, propKey);
  }
}

其中的 registrationNameModules这个变量,里面存在一大堆的属性,都是与 React的事件相关:

例子中的 onClick这个 props显然符合,所以可以执行 ensureListeningTo这个方法:

// react-dom/src/client/ReactDOMComponent.js
function ensureListeningTo(rootContainerElement, registrationName) {
   
  var isDocumentOrFragment = rootContainerElement.nodeType === DOCUMENT_NODE || rootContainerElement.nodeType === DOCUMENT_FRAGMENT_NODE;
  var doc = isDocumentOrFragment ? rootContainerElement : rootContainerElement.ownerDocument;
  listenTo(registrationName, doc);
}

这个方法中,首先判断了 rootContainerElement是不是一个 document或者 Fragment(文档片段节点),示例中传过来的是 .box这个 div,显然不是,所以 doc这个变量就被赋值为 rootContainerElement.ownerDocument,这个东西其实就是 .box所在的 document元素,把这个document传到下面的 listenTo里了,事件委托也就是在这里做的,所有的事件最终都会被委托到 document 或者 fragment上去,大部分情况下都是 document,然后这个 registrationName就是事件名称 onClick

接着开始执行 listenTo方法,这个方法其实就是注册事件的入口了,方法里面有这么一句:

// react-dom/src/events/ReactBrowserEventEmitter.js
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值