当前位置: 首页 > article >正文

React的合成事件

React 16/18合成事件

合成事件对象SyntheticBaseEvent

基于React内部的处理 如果我们给合成事件绑定一个普通函数 当事件触发 绑定的函数执行 方法中的this会是undefined(这样是不好的)

  • 解决方案 是将this -> 实例
  • 我们可以基于JS中的bind方法:预先处理函数中的this和实参
  • 当然更推荐的方法是:将绑定的函数设置为”箭头函数“,让其使用上下文中的this(也就是所说的实例)

合成事件对象SyntheticBaseEvent:React内部经过特殊处理 把各个浏览器的事件对象统一化后 的一个事件对象
我们在React合成事件对象触发的时候 我们也可以获取到事件对象 只不过此对象是合成事件对象
合成事件对象中 也包含了浏览器内置事件对象中的一些属性和方法(常用的基本都有)例如clientX/clientY pageX/pageY target type preventDefault stopPropagation…
nativeEvent:这个属性可以获取浏览器内置(原生)的事件对象

注意:经过bind处理改变this指向 不管有没有预传参数 最后一个实参都是合成对象

事件委托机制

我们先来复习一下事件委托
事件委托:利用事件的传播机制 实现一套事件绑定处理方案
例如:一个容器中 有很多元素都要在点击的时候做一些事情

  • 传统方案:首先获取需要操作的元素 然后逐一做事件绑定
  • 事件委托:只需要给容器做一个事件绑定(点击内部的任何元素 根据事件的冒泡传播机制 都会让容器的点击事件也触发 我们在这里 根据事件源 做不同的事情就可以了)
    优势:
  • 提高JS代码运行的性能 并且把处理的逻辑都集中在一起
  • 某些需求必须基于事件委托处理 例如:动态增加的元素也需要有点击
  • 给动态绑定的元素做事件绑定
    限制:
  • 当前操作的事件必须支持冒泡传播机制才可以
  • 例如:mouseenter/mouseleave等事件是没有冒泡传播机制的
  • 如果单独做的事件绑定 做了事件传播机制的阻止 呢么事件委托中的操作也不会生效

事件具备传播机制
第一步:从最外层向最里层逐一查找 (捕获阶段:分析出路径)
第二部:把事件源(点击的这个元素) 的点击行为触发(目标阶段)
第三步:按照捕获阶段分析出来的路径 从里到外 把每一个元素的点击行为触发(冒泡阶段)
事件和事件绑定

  • 事件是浏览器赋予元素的默认行为
  • 事件绑定给这个行为绑定一个方法

即使我们没有给body的点击事件绑定方法 当我们点击body的时候 其点击行为也会被触发 只不过啥事都不做而已

e.stopPropagation 阻止事件的传播 (包含捕获与冒泡)
e.stopImmediatePropagation() 阻止事件传播 只不过它可以把当前元素绑定的其他方法(同级也会阻止) 如果还未执行 也不会在执行了

合成事件底层实现原理

所谓合成事件绑定 其实并没有给元素自身做事件绑定 而是给元素设置 onXxx/onXxxCapture这样的合成事件属性
当事件行为触发 根据原生事件传播的机制 都会传播到#root容器上 React内部给#root容器做了事件绑定(捕获与冒泡)
当React内部绑定的方法执行的时候 会依据ev.composedPath()中分析的路径 依次把对应阶段的onXxx/onXxxCapture 等事件合成属性触发执行
=>合成事件是利用事件委托(事件传播机制完成的)

因为最后执行的handle方法 从此可以知道为什么 如果不经过处理 方法中的this是undefined (如果绑定的方法是箭头函数 则找函数上级上下文中的this)

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>合成事件原理</title>
		<style>
			* {
				margin: 0;
				padding: 0;
			}
			html,
			body {
				height: 100%;
				overflow: hidden;
			}
			.center {
				position: absolute;
				top: 50%;
				left: 50%;
				transform: translate(-50%, -50%);
			}
			#root {
				width: 300px;
				height: 300px;
				background: lightblue;
			}
			#outer {
				width: 200px;
				height: 200px;
				background: lightgreen;
			}
			#inner {
				width: 100px;
				height: 100px;
				background: lightcoral;
			}
		</style>
	</head>
	<body>
		<div id="root" class="center">
			<div id="outer" class="center">
				<div id="inner" class="center"></div>
			</div>
		</div>
		<script>
			const root = document.querySelector("#root"),
				outer = document.querySelector("#outer"),
				inner = document.querySelector("#inner");

			outer.onClick = () => {
				console.log("outer 冒泡 合成");
			};
			outer.onClickCapture = () => {
				console.log("outer 捕获 合成");
			};
			inner.onClick = () => {
				console.log("inner 冒泡 合成");
			};
			inner.onClickCapture = () => {
				console.log("inner 捕获 合成");
			};

			//给#root做事件绑定 捕获 冒泡 #root上绑定的方法执行把所有规划的路径中 有合成事件属性的都执行即可
			root.addEventListener(
				"click",
				(ev) => {
					let path = ev.composedPath(); //path: [事件源-> ... -> window] 所有祖先元素
					[...path].reverse().forEach((ele) => {
						let handle = ele.onClickCapture; // 获得祖先元素的onClickCapture
						if (handle) {
							handle();
						}
					});
				},
				true
			);
			root.addEventListener(
				"click",
				(ev) => {
					let path = ev.composedPath(); //path: [事件源-> ... -> window] 所有祖先元素
					path.forEach((ele) => {
						let handle = ele.onClick; // 获得祖先元素的onClick
						if (handle) {
							//这里把绑定的合成事件方法执行 如果不经过处理 方法中的this是undefined (如果绑定的方法是箭头函数 则找函数上级上下文中的this)
							//在执行这些方法之前 把原生的事件对象ev做特殊处理 返回合成事件对象
							handle(); 
						}
					});
				},
				false
			);
		</script>
	</body>
</html>

React合成事件

React中合成事件的处理原理

  • ”绝对不是“给当前元素基于addEventListener单独做的事件绑定 React的合成事件 都是基于事件委托处理的
  • 在React17及以后版本 都是委托给#root这个容器(捕获和冒泡都做了委托 并捕获与冒泡分开)
  • 但是在16版本 都是委托给document容器的(而且只做了冒泡阶段的委托 捕获与冒泡没分开)
  • 在16版本 关于合成事件对象的处理 React内部是基于”事件对象池“ 做了一个缓存机制 React17之后是 去掉了这套事件对象缓存池和缓存机制 当每一次事件触发的时候 如果传播到了委托的元素上(document/#root) 在委托的方法中 我们首先会对内置事件对象做统一处理 生成合成事件对象
  • 在React16版本中 为了防止每一次都是重新创建出新的合成事件对象 它设置了一个事件对象池(缓存池) 本次事件触发 获取到事件操作的相关信息后 我们从事件对象池中 获取存储的合成事件对象 把信息赋值给相关的成员 等待本次操作结束 把合成事件对象中的成员信息都清空掉 在放到事件对象池中 容易出现创建的对象信息清空的问题(如设置5秒后在用这些信息 会获取不到) 使用ev.persist() 可以把合成事件中的信息保留下来
  • 对于没有实现事件传播机制的事件 才是单独做的事件绑定(例如: onMouseEnter/onMouseLeave…)

在组件渲染的时候 如果发现jsx元素属性中有 onXxxx/onXxxxCapture这样的属性 不会给当前元素直接做事件绑定 只是把绑定的方法
赋值给元素的相关属性!!
例如:

  • outer.onClick = () => {console.log(‘outer 冒泡 合成’)} //着不是DOM0级事件绑定(这样的才是 outer.onclick)
  • outer.onClickCapture = () => {console.log(‘outer 捕获 合成’)}

然后对#root这个容器做了事件绑定(捕获和冒泡都做了)
原因:因为组件中所渲染的内容 最后都会插入到#root容器中 这样点击页面中任何一个元素 最后都会把#root的点击行为触发
而在给#root绑定的方法中 把之前给元素设置的onXxxx/onXxxCapture属性 在相应的阶段执行!!!

React17及以后的在这里插入图片描述
React16
在这里插入图片描述


http://www.kler.cn/a/6582.html

相关文章:

  • 【机器人】机械臂位置、轨迹和转矩控制概要
  • 常用Python自动化测试框架有哪些?
  • PCL点云库入门——PCL库中点云数据拓扑关系之K-D树(KDtree)
  • 【1.排序】
  • Linux 使用的小细节
  • 《薄世宁医学通识50讲》以医学通识为主题,涵盖了医学的多个方面,包括医学哲学、疾病认知、治疗过程、医患关系、公共卫生等
  • C#收集SMD零件计数器数料机
  • AI大模型争议的背后,是技术以人为本的初衷
  • Tomcat面试题+http面试题+Nginx面试题+常见面试题
  • 读《高效能人士的七个习惯》的一些感悟
  • CSS基础
  • 从零开始实现一个C++高性能服务器框架----协程模块
  • 【MySQL】表的基本约束
  • CSS特殊样式
  • 编译Linux内核一定要知道的几个小Tips
  • 数据结构_第五关:单链表OJ题练习
  • 反射学习总结
  • ABC296E Transition Game
  • c++实现sqlite的增删改查
  • 如何下载ChatGPT-ChatGPT如何写作
  • 中华好诗词(六)
  • ChatGPT 从注册到自建应用
  • Linux【环境变量】
  • Apple Pencil性价比高吗?第三方平替电容笔排名
  • 你好快哦, HikariCP
  • 获取QTableWidget中某个单元格的坐标