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及以后的React的合成事件_第1张图片
React16
React的合成事件_第2张图片

解决移动端click会存在300ms延迟

移动端的click会存在300ms延迟
移动端的click是单击事件
pc端的click是点击
如果连着点击两下:
pc端会触发:两次click、一次dblclick
移动端:不会触发click 只会触发dblclick

单击事件:第一次点击后 检测300ms 看是否有第二次点击操作 如果没有就是单击 如果有就是双击

方法一:
单手指事件模型:touch
touchstart touchmove touchend
模拟点击效果

import React from "react";

class Demo extends React.Component {
	//手指按下:记录手指的起始坐标
	touchstart = (ev) => {
		console.log(ev);
		let finger = ev.changedTouches[0]; //记录了操作手指的相关信息
		this.touch = {
			startX: finger.pageX,
			startY: finger.pageY,
			isMove: false,
		};
	};
	//手指移动:记录手指偏移量 和误差值做对比 分析出是否发生移动
	touchmove = (ev) => {
		let finger = ev.changedTouches[0],
			{ startX, startY } = this.touch;
		let changeX = finger.pageX - startX,
			changeY = finger.pageY - startY;
		if (Math.abs(changeX) > 10 || Math.abs(changeY) > 10) {
			this.touch.isMove = true;
		}
	};
	//手指离开:根据isMove判断是否是点击
	touchend = () => {
		let { isMove } = this.touch;
		if (isMove) return;
		//说明触发了点击操作
		console.log("点击了按钮");
	};
	render() {
		return (
			<div>
				<button
					onTouchStart={this.touchstart}
					onTouchMove={this.touchmove}
					onTouchEnd={this.touchend}
				>
					提交
				</button>
			</div>
		);
	}
}
export default Demo;

方法二:
使用Fastclick解决移动端使用click事件的300ms延迟的问题
Fastclick.attach(document.body);

import React from "react";
import ReactDOM from "react-dom/client";
import "@/index.less";
import "./jsxHandle";
import Demo from "./views/Demo";
import Fastclick from "fastclick";
//使用Fastclick解决移动端使用click事件的300ms延迟的问题
Fastclick.attach(document.body);

const root = ReactDOM.createRoot(document.getElementById("root"));

root.render(
	<>
		<Demo />
	</>
);

你可能感兴趣的:(react18,react.js,javascript,前端)