0

0

对React事件系统的解析

不言

不言

发布时间:2018-07-23 11:17:28

|

1651人浏览过

|

来源于php中文网

原创

这篇文章给大家分享的内容是关于对react事件系统的解析,有一定的参考价值,有需要的朋友可以参考一下。

一 前言

React事件系统有两类:合成事件和原生事件。
在写React组件是我们很容易绑定一个合成事件,但是在一个组件里面是没有办法去绑定另一个组件的合成事件的,此时原生事件就派上了用场。
除了讲述混合(合成事件与原生事件混用)事件,事件冒泡也是我们经常需要处理的事情,这篇文章结合React进行介绍。

二 正文

1.React事件系统

1-1. React 基于 Virtual DOM 实现了一个SyntheticEvent(合成事件)层,我们所定义的事件处理器会接收到一个SyntheticEvent对象的实例,同样支持事件的冒泡机制,我们可以使用stopPropagation()和preventDefault()来中断它。

1-2. 所有事件都自动绑定到最外层上(document)。

2.合成事件绑定机制

在 React 底层,主要对合成事件做了两件事:事件委派和自动绑定。

2-1.事件委派在使用
React 事件前,一定要熟悉它的事件代理机制。它并不会把事件处理函数直接绑定到真实的节点上,而是把所有事件绑定到结构的最外层,使用一个统一的事件监听器,这个事件监听器上维持了一个映射来保存所有组件内部的事件监听和处理函数。当组件挂载或卸载时,只是在这个统一的事件监听器上插入或删除一些对象;当事件发生时,首先被这个统一的事件监听器处理,然后在映射里找到真正的事件处理函数并调用。这样做简化了事件处理和回收机制,效率也有很大提升。

2-2.自动绑定
在React 组件中,每个方法的上下文都会指向该组件的实例,即自动绑定this为当前组件。而且 React 还会对这种引用进行缓存,以达到 CPU 和内存的最优化。

3.在React中使用原生事件

React 架构下也可以使用原生事件。React 提供了完备的生命周期方法,其中componentDidMount会在组件已经完成安装并且在浏览器中存在真实的 DOM 后调用,此时我们就可以完成原生事件的绑定。
但是React不会自动管理原生事件,所以需要你在卸载组件的时候注销掉原生事件

4.合成事件与原生事件混用

书中讲到(这里不做过多介绍):

  • 不要将合成事件与原生事件混用

  • 通过e.target判断来避免

重点是下面这段话,这也是我们今天要着重解决的问题:

用reactEvent.nativeEvent.stopPropagation()来阻止冒泡是不行的。阻止 React 事件冒泡的行为只能用于 React 合成事件系统中,且没办法阻止原生事件的冒泡。反之,在原生事件中的阻止冒泡行为,却可以阻止 React 合成事件的传播。

5.React stopPropagation 与 stopImmediatePropagation

事件冒泡机制:
图片描述

通过 React 绑定的事件,其回调函数中的 event 对象,是经过 React 合成的 SyntheticEvent,与原生的 DOM 事件的 event 不是一回事。准确地说,在 React 中,e.nativeEvent 才是原生 DOM 事件的那个 event。


React 合成事件与原生事件执行顺序图:
图片描述

从图中我们可以得到一下结论:
(1)DOM 事件冒泡到document上才会触发React的合成事件,所以React 合成事件对象的e.stopPropagation,只能阻止 React 模拟的事件冒泡,并不能阻止真实的 DOM 事件冒泡
(2)DOM 事件的阻止冒泡也可以阻止合成事件原因是DOM 事件的阻止冒泡使事件不会传播到document上
(3)当合成事件和DOM 事件 都绑定在document上的时候,React的处理是合成事件应该是先放进去的所以会先触发,在这种情况下,原生事件对象的 stopImmediatePropagation能做到阻止进一步触发document DOM事件

图片描述

stopImmediatePropagation :如果有多个相同类型事件的事件监听函数绑定到同一个元素,则当该类型的事件触发时,它们会按照被添加的顺序执行。如果其中某个监听函数执行了 event.stopImmediatePropagation()方法,则剩下的监听函数将不会被执行

6.React 阻止冒泡总结

(1)阻止合成事件间的冒泡,用e.stopPropagation();
(2)阻止合成事件与最外层document上的事件间的冒泡,用e.nativeEvent.stopImmediatePropagation();
(3)阻止合成事件与除最外层document上的原生事件上的冒泡,通过判断e.target来避免,代码如下:

WaStar 网上花店系统
WaStar 网上花店系统

系统特点: 商品多级分类检索、搜索,支持同一商品多重分类,自由设置显示式样 自由设置会员类型,自由设置权限项目,自由分配每种会员类型和每个会员的权限 灵活的商品定价,最多12级价格自由分配给各种会员类型或会员,也可针对单会员单商品特殊定价 强大的会员管理、帐户管理、订单管理功能和一系列帐务查询统计功能 灵活的会员积分系统,自由设置每个积分事件的积分计算方法 灵活的网站内容发布、管理系统,每个栏目可

下载
componentDidMount() { 
document.body.addEventListener('click', e => {   
 if (e.target && e.target.matches('p.code')) {  
      return;    
  }    
  this.setState({   active: false,    });   }); 
 }

7.通过源码看本质

7-1 事件注册
事件注册即在 document 节点,将 React 事件转化为 DOM 原生事件,并注册回调。

// enqueuePutListener 负责事件注册。
// inst:注册事件的 React 组件实例
// registrationName:React 事件,如:onClick、onChange
// listener:和事件绑定的 React 回调方法,如:handleClick、handleChange
// transaction:React 事务流,不懂没关系,不太影响对事件系统的理解
function enqueuePutListener(inst, registrationName, listener, transaction) {
    ... ...
   // doc 为找到的 document 节点
    var doc = isDocumentFragment ? containerInfo._node : containerInfo._ownerDocument;
    // 事件注册
    listenTo(registrationName, doc);
    // 事件存储,之后会讲到,即存储事件回调方法
    transaction.getReactMountReady().enqueue(putListener, {
        inst: inst,
        registrationName: registrationName,
        listener: listener
    });
}

来看事件注册的具体代码,如何在 document 上绑定 DOM 原生事件。

// 事件注册
// registrationName:React 事件名,如:onClick、onChange
// contentDocumentHandle:要将事件绑定到的 DOM 节点
listenTo: function (registrationName, contentDocumentHandle) {
    // document
    var mountAt = contentDocumentHandle;      
    // React 事件和绑定在根节点的 topEvent 的转化关系,如:onClick -> topClick
    var dependencies = EventPluginRegistry.registrationNameDependencies[registrationName];
    
    for (var i = 0; i < dependencies.length; i++){
        // 内部有大量判断浏览器兼容等的步骤,提取一下核心代码
        var dependency = dependencies[i];
        
        // topEvent 和原生 DOM 事件的转化关系
        if (topEventMapping.hasOwnProperty(dependency)) {
            // 三个参数为 topEvent、原生 DOM Event、Document
            // 将事件绑定到冒泡阶段
            trapBubbledEvent(dependency, topEventMapping[dependency], mountAt);
        }
    }
}

来看将事件绑定到冒泡阶段的具体代码:

// 三个参数为 topEvent、原生 DOM Event、Document(挂载节点)
trapBubbledEvent: function (topLevelType, handlerBaseName, element) {
    if (!element) {
        return null;
    }
    return EventListener.listen(element, handlerBaseName, ReactEventListener.dispatchEvent.bind(null, topLevelType));
}

// 三个参数为 Document(挂载节点)、原生 DOM Event、事件绑定函数
listen: function listen(target, eventType, callback) {
    // 去除浏览器兼容部分,留下核心后
    target.addEventListener(eventType, callback, false);
    // 返回一个解绑的函数
    return {
        remove: function remove() {
            target.removeEventListener(eventType, callback, false);
        }
    }
}

在 listen 方法中,我们终于发现了熟悉的 addEventListener 这个原生事件注册方法。只有 document 节点才会调用这个方法,故仅仅只有 document 节点上才有 DOM 事件。这大大简化了 DOM 事件逻辑,也节约了内存。

7-2.事件存储
事件注册之后,还需要将事件绑定的回调函数存储下来。这样,在触发事件后才能去寻找相应回调来触发。在一开始的代码中,我们已经看到,是使用 putListener 方法来进行事件回调存储。

// inst:注册事件的 React 组件实例
// registrationName:React 事件,如:onClick、onChange
// listener:和事件绑定的 React 回调方法,如:handleClick、handleChange
putListener: function (inst, registrationName, listener) {
    // 核心代码如下
    // 生成每个组件实例唯一的标识符 key
    var key = getDictionaryKey(inst);
    // 获取某种 React 事件在回调存储银行中的对象
    var bankForRegistrationName = listenerBank[registrationName] || (listenerBank[registrationName] = {});
    bankForRegistrationName[key] = listener;
}

7-3.事件执行

  • 每次触发事件都会执行根节点上 addEventListener 注册的回调,也就是 ReactEventListener.dispatchEvent 方法,事件分发入口函数。该函数的主要业务逻辑如下:

    • 根据 DOM 事件构造 React 合成事件。

    • 将合成事件放入队列。

    • 批处理队列中的事件(包含之前未处理完的,先入先处理)

    • 找到事件触发的 DOM 和 React Component

    • 从该 React Component,调用 findParent 方法,遍历得到所有父组件,存在数组中。

    • 从该组件直到最后一个父组件,根据之前事件存储,用 React 事件名 + 组件 key,找到对应绑定回调方法,执行,详细过程为:

React合成事件的冒泡并不是真的冒泡,而是节点的遍历。

三 后记

个人觉得stopImmediatePropagation非常有用,很有必要阻止合成事件冒泡到DOM document上,原因是:

1.合成事件本来就绑定在document上,完全可以获取这个document
2.stopImmediatePropagation可以阻止触发的document DOM上的事件,这十分有必要
3.不会阻止DOM 上的事件冒泡到document DOM

相关推荐:

Vue常用的三种传值方式

React Event事件注册的实现

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
俄罗斯Yandex引擎入口
俄罗斯Yandex引擎入口

2026年俄罗斯Yandex搜索引擎最新入口汇总,涵盖免登录、多语言支持、无广告视频播放及本地化服务等核心功能。阅读专题下面的文章了解更多详细内容。

75

2026.01.28

包子漫画在线官方入口大全
包子漫画在线官方入口大全

本合集汇总了包子漫画2026最新官方在线观看入口,涵盖备用域名、正版无广告链接及多端适配地址,助你畅享12700+高清漫画资源。阅读专题下面的文章了解更多详细内容。

17

2026.01.28

ao3中文版官网地址大全
ao3中文版官网地址大全

AO3最新中文版官网入口合集,汇总2026年主站及国内优化镜像链接,支持简体中文界面、无广告阅读与多设备同步。阅读专题下面的文章了解更多详细内容。

38

2026.01.28

php怎么写接口教程
php怎么写接口教程

本合集涵盖PHP接口开发基础、RESTful API设计、数据交互与安全处理等实用教程,助你快速掌握PHP接口编写技巧。阅读专题下面的文章了解更多详细内容。

1

2026.01.28

php中文乱码如何解决
php中文乱码如何解决

本文整理了php中文乱码如何解决及解决方法,阅读节专题下面的文章了解更多详细内容。

3

2026.01.28

Java 消息队列与异步架构实战
Java 消息队列与异步架构实战

本专题系统讲解 Java 在消息队列与异步系统架构中的核心应用,涵盖消息队列基本原理、Kafka 与 RabbitMQ 的使用场景对比、生产者与消费者模型、消息可靠性与顺序性保障、重复消费与幂等处理,以及在高并发系统中的异步解耦设计。通过实战案例,帮助学习者掌握 使用 Java 构建高吞吐、高可靠异步消息系统的完整思路。

8

2026.01.28

Python 自然语言处理(NLP)基础与实战
Python 自然语言处理(NLP)基础与实战

本专题系统讲解 Python 在自然语言处理(NLP)领域的基础方法与实战应用,涵盖文本预处理(分词、去停用词)、词性标注、命名实体识别、关键词提取、情感分析,以及常用 NLP 库(NLTK、spaCy)的核心用法。通过真实文本案例,帮助学习者掌握 使用 Python 进行文本分析与语言数据处理的完整流程,适用于内容分析、舆情监测与智能文本应用场景。

23

2026.01.27

拼多多赚钱的5种方法 拼多多赚钱的5种方法
拼多多赚钱的5种方法 拼多多赚钱的5种方法

在拼多多上赚钱主要可以通过无货源模式一件代发、精细化运营特色店铺、参与官方高流量活动、利用拼团机制社交裂变,以及成为多多进宝推广员这5种方法实现。核心策略在于通过低成本、高效率的供应链管理与营销,利用平台社交电商红利实现盈利。

122

2026.01.26

edge浏览器怎样设置主页 edge浏览器自定义设置教程
edge浏览器怎样设置主页 edge浏览器自定义设置教程

在Edge浏览器中设置主页,请依次点击右上角“...”图标 > 设置 > 开始、主页和新建标签页。在“Microsoft Edge 启动时”选择“打开以下页面”,点击“添加新页面”并输入网址。若要使用主页按钮,需在“外观”设置中开启“显示主页按钮”并设定网址。

52

2026.01.26

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React.JS中文基础视频教程
React.JS中文基础视频教程

共14课时 | 3万人学习

React 教程
React 教程

共58课时 | 4.2万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 2.5万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号