react事件系统(老版本)

news2025/7/18 11:50:46

带着问题阅读探索

  • React 为什么有自己的事件系统?
  • 什么是事件合成 ?
  • 如何实现的批量更新?
  • 事件系统如何模拟冒泡和捕获阶段?
  • 如何通过 dom 元素找到与之匹配的fiber?
  • 为什么不能用 return false 来阻止事件的默认行为?
  • 事件是绑定在真实的dom上吗?如何不是绑定在哪里?
  • V17 对事件系统有哪些改变?

React为什么要有自己的事件系统。

  • 首先,对于不同的浏览器,对事件存在不同的兼容性,React 想实现一个兼容全浏览器的框架, 为了实现这个目标就需要创建一个兼容全浏览器的事件系统,以此抹平不同浏览器的差异。
  • 其次,react将事件统一绑定到根容器上(17之前是document),防止过多事件直接绑定到原生dom上,这样的方式不仅仅减少了内存的消耗,还能在组件挂在销毁时统一订阅和移除事件,也有利于一个 html 下存在多个应用(微前端)
  • 竟然是统一绑定的事件,那么react就需要自己实现一套事件流,事件俘获-》事件源=〉事件冒泡,也包括事件对象event的重写。

什么是事件合成

  • 上面已经知道react注册事件的时候会将所有事件注册到document/根容器上。
  • 对于click,会绑定到document上。
  • 而对于input的onChange事件,绑定到document上面的就多了blur,change ,focus ,keydown,keyup 等事件。
  • react绑定事件并不是一次性绑定所有事件,比如发现了onClick,就绑定click事件,发现onChange,就绑定[blur,change ,focus ,keydown,keyup] 多个事件。
  • 事件合成的概念就是:React应用中,事件并不是原生的事件,而是由react合成的事件,比如onCLick由click合成,而onChange由blur,change ,focus ,keydown,keyup 等事件合成。

事件系统

一共分为三部分:

  • 事件合成系统,初始化注册不同的事件插件。
  • 在一次渲染过程中,对事件标签中事件的收集,往container注册事件。
  • 一次用户交互,事件触发,到时间执行一系列过程。

事件插件机制

react对于不同的事件,比如onClick和onChange,会有不同的事件插件处理。

const registrationNameModules = {
    onBlur: SimpleEventPlugin,
    onClick: SimpleEventPlugin,
    onClickCapture: SimpleEventPlugin,
    onChange: ChangeEventPlugin,
    onChangeCapture: ChangeEventPlugin,
    onMouseEnter: EnterLeaveEventPlugin,
    onMouseLeave: EnterLeaveEventPlugin,
    ...
}

registrationNameModules存放着每个事件与之对应的处理插件的映射。比如onClick,就是SimpleEventPlugin。对于onChange,会使用ChangeEventPlugin处理…

在这里插入图片描述
看看SimpleEventPlugin和ChangeEventPlugin的真面目。
在这里插入图片描述
SimpleEventPlugin是一个对象,registerSimpleEvents顾名思义就是用来注册simple的事件的。
在这里插入图片描述
在这里插入图片描述
可以看到,遍历simpleEventPluginEvents数组,如keyUp,mouseDown都属于simple的事件。然后对每个事件,调用
register('keyUp', 'onKeyUp')
第一个参数就是原生事件,第二个参数就是react的事件。
然后调用registerTwoPhaseEvent( 'onKeyUp', ['keyUp'])
在这里插入图片描述
这就是simpleEventPlugin的registerEvents做的事情。
而onChange对应的ChangeEventPlugin
在这里插入图片描述
注册onChange,而onChange依赖的事件是change,click,…等诸多个原生事件。
而每个插件的extractEvents顾名思义就是提取事件源对象event。对于不同的事件,事件源对象不同。

而这也解释了为什么要用不同的插件对象处理事件?

对于不同的合成事件,有不同的处理逻辑;对应的事件源对象也不同,react事件和事件源是自己合成的。

而还有一个对象registrationNameDependencies,
在这里插入图片描述
就是上面说到每个插件调用registerEvent的时候,最终调用的是registerDirectEvent,而他会将事件的依赖收集到registrationNameDependecies上面,比如上面的onKeyup: [keyUp],而onChange就是对应: ['blur,‘change’…]
最终这个对象就长得像

{
    onBlur: ['blur'],
    onClick: ['click'],
    onClickCapture: ['click'],
    onChange: ['blur', 'change', 'click', 'focus', 'input', 'keydown', 'keyup', 'selectionchange'],
    onMouseEnter: ['mouseout', 'mouseover'],
    onMouseLeave: ['mouseout', 'mouseover'],
    ...
}

这个对象保存了React事件和原生事件的对应关系,发现有react事件的时候,就会通过这个对象找到原生事件数组,然后逐一绑定。

事件绑定

react在处理props的啥时候,如果遇到了事件比如onCLick,就会通过addEventListener注册原生事件。
然后我们绑定的事件比如

const Test = () => {
return <div onClick={handleClick}>123</div>
}

handleClick事件,他会存在div fiber上面的memoizedProps上面如

testFiber.child = divFiber
divFiber.child = testFiber
divFiber.memoizedProps = {onClick: handleClick}

接着需要通过react事件注册原生的事件。
react在处理props的时候,如果发现是合成事件的话,比如onClick,就会调用legacyListenToEvent 函数,

function diffProperties(){
    /* 判断当前的 propKey 是不是 React合成事件 */
    if(registrationNameModules.hasOwnProperty(propKey)){
         /* 这里多个函数简化了,如果是合成事件, 传入成事件名称 onClick ,向document注册事件  */
         legacyListenToEvent(registrationName, document);
    }
}

在这里插入图片描述
上面介绍了registrationNameDependencies是存取react事件跟原生事件的绑定。如onChange对应[‘blur’, ‘change’, ‘click’, ‘focus’, ‘input’, ‘keydown’, ‘keyup’, ‘selectionchange’],通过react事件获取原生事件,通过for循环进行绑定。

其次,真正绑定到container上面的函数,并不是我们直接写的handleClcik函数,而是React统一的事件处理还能输,dispatchevent,只要是react事件触发,首先执行的就是dispatchEvent
在这里插入图片描述
给容器注册事件的时候,通过.bind传入了一些参数,这样dispatchEvent才能知道是什么事件触发。

最后则是事件触发

老的事件版本,使用的批量更新依然是通过开关开启或者关闭的。

第一步

比如点击一次之后,
dispatchEvent触发,传入真实的事件源button元素本身。
然后通过button dom可以找到对应的fiber
因为dom跟fiber之间的关系是

fiber.stateNode = dom
dom.xxxKey = fiber

伪代码类似于

export function batchedEventUpdates(fn,a){
    isBatchingEventUpdates = true; //打开批量更新开关
    try{
       fn(a)  // 事件在这里执行
    }finally{
        isBatchingEventUpdates = false //关闭批量更新开关
    }
}
第二步 合成事件源

我们知道react事件的事件源也是react自己合成的,并不是原生的事件源。
onClick是由SimpleEventPlugin处理的,
在这里插入图片描述
对于onCLick,他的事件源就是SyntheticMouseEvent。所以第二阶段的模型就是:
图片来自https://juejin.cn/book/6945998773818490884/section/6959723748450631694
(图片来自掘金大佬的课程 https://juejin.cn/book/6945998773818490884/section/6959723748450631694)

第三步,形成事件执行队列。

在第一步通过dom获取的fiber,从这个ifber往上遍历,遇到是元素类型的,比如div,p这些,就会通过一个数组来收集事件。

  • 对于俘获事件如onClickCapture,就会unshift放在数组前面,以此模拟事件俘获阶段。
  • 如果遇到冒泡事件onClick,就直接push在后面,模拟事件冒泡阶段。
  • 一直收集到container,形成一个队列,在接下来的阶段,依次执行队列里的函数。
    在这里插入图片描述
    事件手机的逻辑,首先通过fiber.stateNode获取dom,然后通过getListener获取对应的注册事件。
    在这里插入图片描述
    通过fiber.stateNode获取上面的props,然后获取对应注册的注册事件。
    接着如果是capture的,就unshift进数组,否则就push进数组,while循环直到收集结束。
    那么对于以下demo
export default function Test(){
    const handleClick1 = () => console.log(1)
    const handleClick2 = () => console.log(2)
    const handleClick3 = () => console.log(3)
    const handleClick4 = () => console.log(4)
    return <div onClick={ handleClick3 }  onClickCapture={ handleClick4 }  >
        <button onClick={ handleClick1 }  onClickCapture={ handleClick2 }  >点击</button>
    </div>
}

点击button
数组的顺序应该是: [ handleClick4, handleClick2, handleClick1, handleClick3 ]。
在这里插入图片描述

React如何阻止事件冒泡呢?

先看看事件如何执行的,react提供了event.isPropagationStopped来判断是否已经阻止事件冒泡。
在这里插入图片描述
数组通过for循环依次执行,而当调用event.stopPropagation之后,后面一个事件的执行event.isPropagationStopped就是true了,就会退出for循环,后面的函数不再执行,以此模拟阻止事件冒泡。

问答

React 为什么有自己的事件系统? 什么是事件合成?
  • react有自己的一套事件系统,第一是为了兼容所有的浏览器,因为各浏览器之间的事件源可能不同。其次,react将所有事件注册到根容器上,,在挂载和卸载的时候可以方便统一处理,其次在17之前,react将所有事件注册到document上面,而在17之后,react将所有事件注册到根容器上,这样可以方便多个应用如微前端的接入。
  • 其次,对于onClick,onChange等react事件,他们并不是原生的事件,比如onChange是由blur, keyup,focus,click等事件合成的,而onClick是由clikc事件合成的。对于不同的事件,react使用不同的插件进行注册,比如onCLick事件是用SimpleEventPlugin处理的,而onChange是由changeEventPlugin处理的。
如何模拟事件捕获和事件冒泡阶段? 为什么不能用 return false 来阻止事件的默认行为?
  • react通过一个事件队列来收集事件,从发生事件开始,react通过原生dom获取对应的fiber,然后向上遍历元素类型的fiber,对于同一事件冒泡的处理函数,就直接push进数组,对于同一事件俘获的处理函数,就通过unshift插入数组,以此达到模拟事件俘获和事件冒泡的阶段
  • 而阻止事件冒泡react提供了event.stopPropagiton函数,react执行事件队列是通过for循环的,event.isStopPropagation可以判断是否执行了event.stopPropagiton,一旦判断执行了之后就break退出for循环,那么事件队列后面的事件不会执行,以此达到阻止冒泡的效果。
一次点击到事件执行都发生了什么?
  • 1 一次点击之后,首先执行dispatchEvent函数,传入对应的dom,通过dom获取fiber
  • 2 然后通过registionNameMOdule对象,获取对应的插件,比如onCLick对应的是SimpleEventPlugin,然后通过插件获取onClick事件源,不同的事件事件源不同。
  • 3 从当前fiber往上遍历元素类型的fiber,维护一个数组收集事件,对于俘获事件直接unshift,对于冒泡事件直接push,最终得到一个事件队列。
  • 4 for循环执行事件队列,判断event.isStopPropagation,如果true表示阻止冒泡,直接break退出for循环,后面的事件不再执行。

17版本之前的事件,虽说模拟了事件冒泡和俘获,但是本质上,执行的时机,都是在冒泡阶段。而新版本的事件处理,修正了这个问题。

  • 参考掘金的 《react进阶实践指南》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/28946.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python【PyQt5】的环境搭建和使用(全网最全)其一

什么是pyQT pyqt是一个用于创建GUI应用程序的跨平台工具包&#xff0c;它将python与qt库融为一体。也就是说&#xff0c;pyqt允许使用python语言调用qt库中的API。这样做的最大好处就是在保存了qt高运行效率的同时&#xff0c;大大提高开发效率。因为&#xff0c;使用python语言…

城市路边停车收费系统/停车收费管理系统

摘 要 近年来&#xff0c;随着社会的进步和发展&#xff0c;车辆也在迅速增加&#xff0c;城市交通的瓶颈不仅体现在道路交通的拥挤上&#xff0c;也体现在传统停车场管理效率和安全性大大滞后于社会的需要&#xff0c;给人们的生活带来了极大的不便。尤其&#xff0c;随着汽车…

二、MongoDB简介及基本操作

mongodb是一个基于文档的强大、灵活、易于扩展的通用型数据库。是基于分布式文件存储的数据库。其由 C 语言编写。旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。 mongodb也是一个介于关系数据库和非关系数据库之间的产品&#xff0c;是非关系数据库当中功能最丰富&…

运动健身买什么耳机好用、最优秀的健身耳机推荐分享

冬天绝对是个减肥的好季节&#xff0c;因为这个季节天气比较冷&#xff0c;我们在运动过程中消耗的热量也就会更多&#xff0c;因此选择一款不错的运动耳机来用坚持就显得尤为重要了。这款运动耳机要能稳定在耳朵上&#xff0c;还要具备防水功能&#xff0c;同时音质上也要有保…

闲人闲谈PS之三十四——项目成本费用控制阈值

**惯例闲话&#xff1a;**最近有小伙伴问闲人有没有PS顾问资源&#xff0c;闲人问了一圈&#xff0c;结果发现都没有档期&#xff0c;不免让闲人有些失落&#xff0c;好心答应帮忙&#xff0c;结果帮不上…但是隐隐约约觉得在几年前说的话被应验了&#xff0c;PS模块一定是个热…

Ubuntu G++ 编译C++源文件

工程项目代码简短的时候使用 G 进行功能模块测试 过程分为&#xff1a; 预处理&#xff1a;展开头文件&#xff0c;去掉主食&#xff0c;条件编译和文件包含编译&#xff1a;检查语法&#xff0c;生成汇编代码汇编&#xff1a;汇编代码转换成机器码链接&#xff1a;Link 主要是…

王学岗音视频开发(一)—————配置NDK开发环境

Android studio准备 Android studio需要下载Android6.0版本(Android SDK Platform 23)&#xff0c;最小支持Android6.0 NDK 下载 cmake下载安装 Android studio 代理配置 dl.google.com可能会被屏蔽&#xff0c;首先查下其IP地址。查到IP地址后修改etc/hosts文件。 Andr…

pytorch深度学习实战lesson25

第二十五课 network in network(NIN) NIN 叫做network in network或者叫做网络中的网络。这个网络现在用的不多&#xff0c;几乎很少被用到。但是它里面提出了比较重要的概念&#xff0c;在之后很多网络都会被持续的用到。所以今天认识一下这一个网络。 目录 理论部分 实践部…

高并发下丢失更新的解决方案

作者&#xff1a;谢益培 1 背景 关键词&#xff1a;并发、丢失更新 预收款账户表上有个累计抵扣金额的字段&#xff0c;该字段的含义是统计商家预收款账户上累计用于抵扣结算成功的金额数。更新时机是&#xff0c;账单结算完成时&#xff0c;更新累计抵扣金额累计抵扣金额账…

ImmunoChemistry艾美捷Annexin V-FITC细胞凋亡检测试剂盒方案

膜联蛋白V是具有血管抗凝血活性的钙和磷脂结合蛋白家族的成员。体外实验结果表明&#xff0c;它可能通过与凝血酶原竞争磷脂酰丝氨酸&#xff08;PS&#xff09;结合位点而在抑制凝血中发挥作用。在健康细胞中&#xff0c;PS通常保存在细胞膜的内小叶&#xff08;胞质侧&#x…

网络是怎样连接的--TCP大致控制流程

文章目录5.1 协议栈内部结构5.2 套接字作用5.3 创建套接字5.4 两类控制信息5.5 连接操作的实际过程5.6 收发数据5.6.1将http请求消息发给协议栈5.6.2 注意对较大数据进行拆分5.6.3 序号和ACK5.6.4 等待超时时间5.6.5 窗口机制提升效率5.6.6 ACK与窗口合并5.7 断开连接5.8 删除套…

MCE | 新冠 德尔塔病毒

冠状病毒&#xff0c;其表面有突出的棒状尖峰&#xff0c;在电镜下可以观察到像王冠一样的放射状凸起而得名。冠状病毒的基本结构如图 1 所示&#xff0c;包括刺突糖蛋白 (S)、包膜 (E)、膜 (M) 和核衣壳 (N)。 图 1. 冠状病毒结构2020 年石正丽教授在 Nature 发表的论文 A …

浅识JVM

⭐️前言⭐️ 本篇文章&#xff0c;博主分享的是在面试中JVM常考的考点&#xff0c;希望这篇文章能够对你有用。 &#x1f349;博客主页&#xff1a; &#x1f341;【如风暖阳】&#x1f341; &#x1f349;精品Java专栏【JavaSE】、【备战蓝桥】、【JavaEE初阶】、【MySQL】、…

多维度比对三种软件开发方式后,无代码开发到底赢在哪?

近年来受信息化、数字化和 5G、云计算发展的影响&#xff0c;以及企业管理的不确定性和复杂性增加&#xff0c;国内管理软件的需求及行业市场整体规模也连年高速增长&#xff0c;进而带动了管理软件开发方式的变革。 但是由于传统自主及外包开发方式存在的周期长、成本高、不能…

DataX二次开发——(9)新增s3reader和s3writer模块

1 背景 DataX3.0支持阿里的OSS的读写&#xff0c;但没支持S3的读写&#xff0c;虽然OSS的也是基于S3协议去做二开的&#xff0c;但是一些参数有点区别&#xff0c;所以按照阿里的OSSReader和OSSWriter开发了S3Reader和S3Writer。 2 代码开发 2.1 s3reader 2.1.1 项目结构 2…

pytorch快速上手(8)-----pytorch优化器简介

文章目录一、简介二、optimizer属性方法1. zero_grad()2. step()3. add_param_group()4. state_dict()5. load_state_dict()学习率动量三、常见优化器介绍1. BGD&#xff08;Batch Gradient Descent&#xff09;2. Stochastic Gradient Descent&#xff08;SGD&#xff09;3. M…

记录一次因执行时间过长锁已经释放导致finally块再次unlock引发的异常

一、前言 因为我的一个需求需要请求一个耗时比较长的接口&#xff08;耗时长其实是对接方的锅&#xff09;&#xff0c;该接口交给了Spring事务管理&#xff0c;并且使用了分布式锁&#xff0c;但是在请求的时候&#xff0c;出现error,看日志发现是unlock的时候没有锁可以去解…

牛客网语法篇练习复合类型(二)

1.输入NxM矩阵&#xff0c;矩阵元素均为整数&#xff0c;计算其中大于零的元素之和。 a,b map(int,input().split()) list1 [] sum 0 for i in range(a):list1.extend(list(map(int,input().split()))) for i in list1:if i>0:sumi print(sum) 2.给你一个整数n&#xff…

辨别代码能否引发线程安全问题--避免在平时写代码时引发线程安全问题

前景提要&#xff1a; 本篇文章只是入门&#xff0c;目的在于在脑海中构建一个Java运行的模型&#xff0c;然后可以在平时写代码时对是否引发线程安全问题有感知。 文章目录引入了解辨别线程安全问题之前先来构建一个计算机运行模型了解线程安全问题怎么能不知道线程和进程了解…

搭建repo服务器管理多个git工程

参考自&#xff1a;搭建repo服务器管理多个git工程     repo系列讲解 —— Android系统源码(AOSP)下载 1、repo介绍 Android使用git作为代码管理工具&#xff0c;开发了gerrit进行代码审核&#xff0c;以便更好的对代码进行集中式管理。还开发了repo命令行工具&#xff0…