react合成事件与原生事件区别备忘

朋友问起在做一个下拉框组件,下拉的点击事件是用react的onClick触发,外部区域点击关闭则用dom的原生点击事件绑定,问题是下拉的点击事件无法阻止冒泡到dom的原生事件。

我说,react的合成事件 和 原生事件是不一样的,尽可能不要混用,不然很绕。翻开之前在codepen写的demo

https://codepen.io/shellphon-the-encoder/pen/vYPEggK

也把自己绕晕了一下。

react的合成事件,注入onClick等事件,是在根元素上事件代理模拟的。react 16.8.0和之前的版本,是在document上事件代理,react 17则是在root

以demo上的div结构为例:v17.0.2

const {useState, useEffect, useRef} = React;
document.getElementById('root').addEventListener('click', (e) =>{console.log('外部原生root 点击', e);//e.stopPropagation();});
document.addEventListener('click', (e) =>{console.log('外部原生document 点击', e);}); 
const App = () => {const sonRef = useRef(null);const parentRef = useRef(null);const parentClick = (e)=>{console.log('合成事件parent click',e);//e.stopPropagation();};const sonClick = (e)=>{console.log('合成事件son click', e);}const sonClickNo = (e)=>{console.log('合成事件son click并阻止冒泡', e);e.stopPropagation();}useEffect(() => {document.addEventListener('click', (e) =>{console.log('内部原生document click', e);}); document.getElementById('root').addEventListener('click', (e) =>{console.log('内部原生 root click', e);});parentRef.current.addEventListener('click', (e) =>{console.log('内部原生事件ref p', e);});sonRef.current.addEventListener('click', (e) =>{console.log('内部原生事件ref son', e);});}, [])return <div ref={parentRef} onClick={parentClick}><div onClick={sonClick}>son</div><div onClick={sonClickNo} ref={sonRef}>son no</div></div>
};ReactDOM.render(<App />, document.getElementById('root'));

document>root>div>.parent>.son

son上的onClick(合成事件),实际是react root上的点击事件,在内部做模拟冒泡。

因为demo写的事件比较多,比较绕,所以画了出来。

当只有合成事件的时候,无非就是 son点击响应,然后parent点击响应。

同一个元素的原生事件和合成事件

当往son原生div加click事件时,点击son,会先响应原生click,再然后才是去合成事件,这中间如果parent也有原生click,那也是先原生click再到合成事件去。

如下:

const {useState, useEffect, useRef} = React;const App = () => {const sonRef = useRef(null);const parentRef = useRef(null);const parentClick = (e)=>{console.log('合成事件parent click',e);//e.stopPropagation();};const sonClick = (e)=>{console.log('合成事件son click', e);}const sonClickNo = (e)=>{console.log('合成事件son click并阻止冒泡', e);e.stopPropagation();}useEffect(() => {parentRef.current.addEventListener('click', (e) =>{console.log('内部原生事件ref p', e);});sonRef.current.addEventListener('click', (e) =>{console.log('内部原生事件ref son', e);});}, [])return <div ref={parentRef} onClick={parentClick}><div onClick={sonClick}>son</div><div onClick={sonClickNo} ref={sonRef}>son no</div></div>
};ReactDOM.render(<App />, document.getElementById('root'));

点击第一个son时:

可以看到先响应了parent的原生事件,然后才到son的合成事件

合成事件和外部root事件的关系

在react外面给root绑定click事件,看合成事件的顺序

const {useState, useEffect, useRef} = React;
document.getElementById('root').addEventListener('click', (e) =>{console.log('外部原生root 点击', e);//e.stopPropagation();});
document.addEventListener('click', (e) =>{console.log('外部原生document 点击', e);}); 
const App = () => {const sonRef = useRef(null);const parentRef = useRef(null);const parentClick = (e)=>{console.log('合成事件parent click',e);//e.stopPropagation();};const sonClick = (e)=>{console.log('合成事件son click', e);}const sonClickNo = (e)=>{console.log('合成事件son click并阻止冒泡', e);e.stopPropagation();}useEffect(() => {parentRef.current.addEventListener('click', (e) =>{console.log('内部原生事件ref p', e);});sonRef.current.addEventListener('click', (e) =>{console.log('内部原生事件ref son', e);});}, [])return <div ref={parentRef} onClick={parentClick}><div onClick={sonClick}>son</div><div onClick={sonClickNo} ref={sonRef}>son no</div></div>
};ReactDOM.render(<App />, document.getElementById('root'));

点击第一个son时,先原生事件, 然后 到了外部root绑定的事件,再到合成事件的son、parent,再然后是document

合成事件是在root上模拟的,而外部绑定root的事件和这个合成事件也就是在一个dom上多次绑定事件响应,各不相干,顺序上谁先绑定谁先响应,于是外部root原生先响应,再到合成事件的处理。而document是在root的上层,因此document事件是在最后才响应。

如果在组件周期里也给root加一个原生事件响应,那它会在合成事件完成之后才响应,因为它也是给同一个dom绑定的事件之一,只是晚于合成事件。

回到最开始的demo代码,当在son的onClick上阻止冒泡时,它做了两件事情:

1. 阻止了向上冒泡的模拟

2. 调用了原生事件的阻止冒泡 (解释了合成事件阻止冒泡后,为什么document事件没有响应)

由此,如果朋友在react 17版本上document上绑定的事件应该是能被合成事件阻止冒泡的。

但如果在react 16.8.0 合成事件是在document上绑定,那么额外绑定的document事件不会被合成事件阻止冒泡。

参考资料:

https://github.com/youngwind/blog/issues/107

https://mdnice.com/writing/85c044f9087746dcbd719e4a0b847278

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

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

相关文章

三、fpga对完成过滤和校验的有效包数据进行有效像素提取、MATLAB对数据源进行处理与下发(完整实现pc机→显示器通信链路)

前言:上篇文章实现了MATLAB模拟发送UDP以太网协议数据包到fpga,能实现双沿数据→单沿数据转换,并将转换后的数据进行包过滤和crc校验,本篇内容要实现真正的从pc机下发视频数据,经过千兆以太网传输存储到fpga 的ddr3中,然后通过hdmi读出到显示屏上。 文章目录 一、模块设…

【Qt学习笔记】connect函数的使用方法总结

connect函数的使用方法总结 一&#xff0c;简介二&#xff0c;connect函数的标准格式&#xff1a;三&#xff0c;参数的含义四&#xff0c;示例五&#xff0c;注意 一&#xff0c;简介 在Qt框架中&#xff0c;connect函数用于连接信号和槽&#xff0c;是Qt信号和槽机制的核心。…

鸿蒙入门06-常见装饰器( 简单装饰器 )

装饰器是鸿蒙开发中非常重要的一个环节因为在很多地方我们都需要用到装饰器并且如果我们想高度的复用, 那么装饰器就是必不可少的一环接下来我们就来介绍一些常见的装饰器注意 : 所有装饰器首字母大写 Entry 用来装饰 struct 使用表示页面的入口 Component 装饰 struct, …

linux二元比较操作符

Linux中如果要比较两个变量或数字&#xff0c;常用二元比较操作符&#xff1b;对于整数之间的比较或字符串之间的比较会有所区别&#xff0c;梳理如下&#xff0c;供大家参考使用&#xff1a; 1.整数比较 二元比较操作符说明备注-eq等于if [ "$a" -eq "$b&quo…

基于Springboot的论坛管理系统

基于SpringbootVue的论坛管理系统的设计与实现 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringbootMybatis工具&#xff1a;IDEA、Maven、Navicat 系统展示 用户登录 首页 公告 热门帖子 论坛新天地 新闻资讯 留言反馈 后台登录 用户管理 公告管理…

java宠物领养系统的设计与实现(springboot+mysql+源码)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的宠物领养系统的设计与实现。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 基于Spring Bo…

JavaWeb--04YApi,Vue-cli脚手架Node.js环境搭建,创建第一个Vue项目

04 1 Yapi2 Vue-cli脚手架Node.js环境搭建配置npm的全局安装路径 3 创建项目&#xff08;这个看下一篇文章吧&#xff09; 1 Yapi 前后端分离中的重要枢纽"接口文档",以下一款为Yapi的接口文档 介绍&#xff1a;YApi 是高效、易用、功能强大的 api 管理平台&#…

Springboot引入外部jar包并打包jar包

前言 spring boot项目开发过程中难免需要引入外部jar包&#xff0c;下面将以idea为例说明操作步骤 将需要的jar包导入到项目中 2.在maven中引入jar包 <dependency><groupId>com</groupId><!--随便填的文件夹名称--><artifactId>xxx</artif…

无人棋牌室为什么频现倒闭潮?

现在小红书抖音上关于棋牌室转让的帖子层出不穷&#xff0c;许多无人棋牌室纷纷倒闭&#xff0c;其背后的原因值得我们深思。 首先&#xff0c;选址的决策至关重要。一些商家可能过于看重场地的面积和价格&#xff0c;而忽视了其他重要的因素。事实上&#xff0c;仅仅因为房子大…

linux内核初始化成功后是如何过渡到android初始化的

Android用的linux内核&#xff0c;以完成OS该有的功能&#xff0c;例如&#xff0c;文件系统&#xff0c;网络&#xff0c;内存管理&#xff0c;进程调度&#xff0c;驱动等 &#xff0c;向下管理硬件资源向上提供系统调用。另一些Android特有驱动也放在内核之中。 当linux内核…

(delphi11最新学习资料) Object Pascal 学习笔记---第10章第1节( 对比其他编程语言中的属性)

10.1.1 对比其他编程语言中的属性 ​ 如果你将这与Java或C#进行比较&#xff0c;在这两种语言中&#xff0c;属性都映射到方法&#xff0c;但是Java中是一种隐式的映射&#xff08;属性基本上是一种约定&#xff09;&#xff0c;而不是显式的编程元素。与Java类似&#xff0c;…

【Qt】Qt安装包、源码、子模块(submodules)下载

1、Qt 4.0 ~ Qt5.14 Qt 4.0 ~ Qt5.14 离线安装包、源码和子模块(submodules)源码下载路径: https://download.qt.io/new_archive/qt/以Qt5.7.1为例,注意子模块都是源码,需要独立编译 2、Qt5.15 ~ Qt6.7 Qt5.15 ~ Qt6.7源码和子模块(submodules)源码下载路径: htt…

微服务OR单体架构

微服务OR单体架构 为什么会出现微服务和单体架构的争议&#xff1f;在实际的业务中&#xff0c;你选择的是微服务还是单体架构&#xff1f;在云上&#xff0c;哪种架构更符合未来云的发展趋势呢? 说到微服务OR单体架构&#xff0c;其实这两个场景并不存在很明确的争议界限的&a…

LabVIEW供热管道泄漏监测与定位

LabVIEW供热管道泄漏监测与定位 在现代城市的基础设施建设中&#xff0c;供热管道系统起着极其重要的作用。然而&#xff0c;管道泄漏问题不仅导致巨大的经济损失&#xff0c;还对公共安全构成威胁。因此&#xff0c;开发一种高效、准确的管道泄漏监测与定位技术显得尤为关键。…

GUI02-在窗口上跟踪并输出鼠标位置(Win32版)

(1) 响应 WM_MOUSEMOVE 消息获得鼠标位置&#xff1b; (2) 响应 WM_PAINT 将鼠标位置输出到窗口中&#xff1b; (3) 学习二者之间的关键步骤&#xff1a;调用 InvalidateRect() 以通知窗口重绘。 零. 课堂视频 在窗口上跟踪输出鼠标位置-Win32版 一、关键知识点 1. BeginPaint…

Android studio 报错无法找到android.support.v4.app.Fragment——终极解决方案

最近搞了一个小工具&#xff0c;UI界面使用了android.support.v4.app.Fragment&#xff0c;然后同事从服务器下载代码后编译报错无法找到android.support.v4.app.Fragment。 从网上找各种文章&#xff0c;国内国外都看了一遍&#xff0c;大部分解决方案原因都是说Android 9之后…

HANA SQL消耗内存和CPU线程的限制参数

HANA再处理大数据表相关的复杂Sql时&#xff0c;如果没有设置Memory和CPU线程上限的话&#xff0c;会将HANA的资源占用殆尽&#xff0c;造成HANA无法响应其他Sql请求&#xff0c;导致表现在应用服务器上就是系统卡顿的情况。解决上述问题的办法就是按照下图设置Memory(图1&…

【解决】Unity 编辑器内打开场景出现引擎崩溃问题

开发平台&#xff1a;2019.4.10f1   问题背景 1&#xff09;修改 Variant Prefab 变体制件时&#xff0c;出现外部引用的模型文件丢失导致引擎崩溃。 2&#xff09;二次打开工程时&#xff0c;在选中 该问题资产、问题资产所应用的场景等 文件对象时&#xff0c;Unity 编辑器…

Rust常见陷阱 | 不太勤快的迭代器

在Rust中,迭代器被设计为惰性的,意味着它们不会立即对所有元素进行操作。相反,只有在消费者(consumer)需要时,元素才会被处理。这种设计可以带来效率上的优势,因为它避免了不必要的计算和存储。然而,这也意味着使用迭代器时必须格外小心,以确保所有的元素都按预期被处…

穿越物联网的迷雾:深入理解MQTT协议

目录标题 1、MQTT简介核心特性 2、MQTT的工作原理通信过程 3、MQTT的消息质量&#xff08;QoS&#xff09;4、安全机制5、实践应用环境准备示例项目发布者客户端订阅者客户端 6、最佳实践7、结论8、参考资料 在物联网&#xff08;IoT&#xff09;的海洋中&#xff0c;数据像水流…