react事件系统(新老版本)

news2024/11/26 12:45:42

带着问题阅读探索

  • 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/27529.html

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

相关文章

SpringCloudGateway--自动路由映射

目录 一、GateWay项目创建 二、子项目创建 三、测试调用 一、GateWay项目创建 首先启动本地nacos&#xff0c;具体可参考&#xff1a;Nacos Windows安装_雨欲语的博客-CSDN博客 新建工程项目spring_cloud_test,pom引入依赖&#xff1a; <parent><groupId>org.s…

单片机只会调库和复制别人的代码是什么水平?

单片机只会调库和复制别人的代码是什么水平&#xff1f;前言什么是调库&#xff1f;如何不调库点亮一个LED调库与不调库的区别为什么要操作寄存器结语前言 相信对于学习过单片机的同学对于调库这个操作都不陌生&#xff0c;大家都是从调别人的库阶段过来的&#xff0c;今天看到…

便捷记账本小程序+后台管理系统-JAVA【数据库设计、源码、开题报告】

第一章 绪 论 1.1选题背景 互联网是人类的基本需求&#xff0c;特别是在现代社会&#xff0c;个人压力增大&#xff0c;社会运作节奏高&#xff0c;随着互联网的快速发展&#xff0c;用户的需求也越来越高&#xff0c;用户也将越来越多依靠互联网而不是自己获取信息&#xff…

Go:进度条工具库 vbauerster/mpb 简介

文章目录简介核心能力示例单进度条渲染多进度条渲染小结简介 mpb是一个在终端进行进度条渲染的工具库 核心能力 支持多进度条 Multiple Bars: Multiple progress bars are supported支持动态设置进度条总值 Dynamic Total: Set total while bar is running支持动态增加 / 删…

【Unity Shader】屏幕后处理4.0:基于高斯模糊的Bloom

原本打算写高斯模糊和双重模糊两个实现Bloom方法的对比&#xff0c;但两个加在一起篇幅过长&#xff0c;于是拆成两篇文章来进行。 学习前建议应先搞清楚的几个概念 HDRLDRToneMapping几种模糊算法1 高斯模糊实现Bloom 最近一直在学习Unity Shader实现各种后处理效果&#x…

c++ - 第13节 - c++中的继承

1.继承的概念及定义 面向对象三大特性&#xff1a;封装、继承、多态注&#xff1a;面向对象不止这三个特性&#xff0c;还有其他特性&#xff0c;比如反射&#xff08;Java中的概念&#xff09;、抽象等封装的理解&#xff1a;&#xff08;1&#xff09;将c设计的stack类&#…

记一次艰难的上班历程

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 以下事件均为卢松松真实经历&#xff1a; 早上7点&#xff0c;小区又被静默了&#xff0c;几百号人堵在小区门口。 我不顾病毒传染的风险挤到了小区门口&#xff0c;问原因。 看门的说到&#x…

pumping lemma

正规语言版本 LLL是正规语言,则存在整数p≥1p\ge 1p≥1 对于任意长度大于等于ppp的字符串w∈Lw\in Lw∈L&#xff0c;wxyzwxyzwxyz,满足下面3个条件 ∣y∣≥1\left|y\right|\ge 1∣y∣≥1 ∣xy∣≤p\left|xy\right|\le p∣xy∣≤p ∀n≥0,xynz∈L\forall n\ge 0,xy^nz\in L∀n≥…

1997-2020年各省三废排放量和熵值法计算的环境规制综合指数(无缺失值)

1997-2020年各省三废排放量和环境规制综合指数 1、包括&#xff1a;30个省份 2、指标包括&#xff1a;工业二氧化硫排放量、工业烟尘排放量和工业废水排放量 环境规制综合指数是由工业废水排放量、工业 SO2 排放量以及工业烟尘排放量计算而来 &#xff08;表格中有详细的三…

网络流量监测与调度技术研究

网络流量监测与调度技术研究网络流量监测与调度技术研究学习目标&#xff1a;流量监测学习内容&#xff1a;流量监测流量监测的设计框架框架一框架二框架三申明&#xff1a; 未经许可&#xff0c;禁止以任何形式转载&#xff0c;若要引用&#xff0c;请标注链接地址。 全文共计…

零基础上手unity VR开发【Oculus账号体系准备】

&#x1f4cc;个人主页&#xff1a;个人主页 ​&#x1f9c0; 推荐专栏&#xff1a;Unity VR 开发成神之路 --【着重解决VR项目开发难&#xff0c;国内资料匮乏的问题。从零入门&#xff0c;一步一个脚印&#xff0c;带你搞定VR开发! &#x1f63b;&#x1f63b;】 &#x1f4d…

大数的乘法

输入一个大正整数和一个非负整数&#xff0c;求它们的积。 输入格式: 测试数据有多组&#xff0c;处理到文件尾。每组测试输入1个大正整数A&#xff08;位数不会超过1000&#xff09;和一个非负整数B&#xff08;int范围&#xff09;。 输出格式: 对于每组测试&#xff0c;…

[Leetcode刷题] - LC003 Longest Substring without repating character

题目链接 Leetcode 003Level up your coding skills and quickly land a job. This is the best place to expand your knowledge and get prepared for your next interview.https://leetcode.com/problems/longest-substring-without-repeating-characters/ 题目描述 给定…

数字先锋 | 随时随地云端阅片,“云胶片”时代来啦!

作为现代医疗必不可少的诊断方法&#xff0c;医学影像数据在医疗数据中的占比高达90%且正以每年30%的速度递增&#xff0c;而影像医生就业人数年增长率仅4%。这意味着&#xff0c;全国总人数不到20万的放射科医生&#xff0c;要处理每年75.4亿人次影像相关诊断需求&#xff0c;…

代理模式-P19

静态代理&#xff1a; 创建项目&#xff1a; Rent package com.Li.demo01;//租房 public interface Rent {public void rent(); }Proxy&#xff1a; package com.Li.demo01; //中介&#xff08;负责找房东&#xff09; public class Proxy implements Rent{//private Host h…

Redis安装及使用(WindowsLinux)

Windows 1.下载 下载地址&#xff1a;Releases tporadowski/redis GitHub。 目前最新5.0.14 2.解压 3.先后打开redis-server.exe和redis-cli.exe两个文件&#xff08;一定要按顺序&#xff09; 4.使用 正常使用在redis-cli.exe里面输入各种命令&#xff08;使用期间redis…

7、系统管理

文章目录7、系统管理7.1 Linux 中的进程和服务7.2 service 服务管理&#xff08;CentOS 6 版本-了解&#xff09;7.2.1 基本语法7.2.2 经验技巧7.2.3 案例实操&#xff08;1&#xff09;查看网络服务的状态&#xff08;2&#xff09;停止网络服务&#xff08;3&#xff09;启动…

基于JAVA的个人博客论坛系统的设计与实现参考【数据库设计、源码、开题报告】

在学校开发搭建一个什么项目最有成就感&#xff0c;那肯定就是搭建「个人博客」呀&#xff0c;然后把自己平时的学习笔记写到博客里&#xff0c;这时你的笔记就是**云笔记**&#xff0c;就再也不会出现因为本地文件丢失而感到痛心的事情。 而且&#xff0c;还可以把你的个人博客…

艾美捷小鼠肿瘤坏死因子α-ELISpot试剂盒使用说明

肿瘤坏死因子-α 肿瘤坏死因子-α&#xff08;TNF-α&#xff0c;也称为TNF-α、TNF-a、TNF-a和肿瘤坏死因子α&#xff09;由许多不同的细胞类型产生&#xff0c;例如单核细胞、巨噬细胞、T细胞和B细胞。TNF-α的许多作用包括保护细菌感染、细胞生长调节、免疫系统调节和感染…

Kotlin 使用vararg可变参数

文章目录背景Kotlin中使用可变参数对Kotlin可变参数反编译资料背景 一般在项目开发中&#xff0c;我们经常会在关键节点上埋点&#xff0c;而且埋点中会增加一些额外参数&#xff0c;这些参数通常是成对出现且参数个数是不固定的。如下&#xff1a; //定义事件EVENT_ID const…