React 合成事件 SyntheticEvent

本文介绍了React的合成事件SyntheticEvent,包括它的概念、事件处理流程、与原生事件的区别,以及React16和17版本中合成事件的不同。通过事件捕获、冒泡和委托的阐述,揭示了React如何优化事件处理并解决浏览器兼容性问题。同时,通过对比React16和17中合成事件的执行顺序变化,展示了React17对事件池的废弃。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 什么是合成事件(SyntheticEvent)?

  • React自己实现了一套事件,在原生事件基础上做了很大改进,减少了内存消耗,解决了IE等浏览器的兼容问题
  • React把事件委托到 document身上(react 17以前),在React17以后再次改进了合成事件,委托对象变成了容器对象
  • 当真实dom元素触发事件,先处理原生事件,然后会冒泡到document或容器对象上,再处理react事件

2. 事件捕获,事件冒泡,事件委托

DOM2级事件规定的事件流包含3个阶段,事件捕获阶段、处于目标阶段、事件冒泡阶段

首先发生的事件捕获为截获事件提供机会,然后是实际的目标接收事件,最后一个阶段是事件冒泡阶段,可以在这个阶段对事件做出响应

  • 事件捕获: 事件捕获会从外向内进行捕获(addEventListener 第三个参数设置为 true可以让事件在 捕获阶段触发)
  • 事件冒泡: 事件冒泡会从内向外冒泡
  • 事件委托: 利用事件冒泡,将事件挂载在外层父级上,当触发子级时会向上冒泡触发父级的方法,再通过 e.target 拿到子级对象

3. React16与17版本合成事件的区别

  • react16时事件委托的对象是 document,react17时事件委托的对象是容器组件
  • react16时原生事件与react事件执行时,冒泡阶段与捕获阶段没有区分开(捕获-> 冒泡 -> 捕获);react17时优化了合成事件的执行,当与原生事件一起调用时,捕获阶段总是先于冒泡阶段(捕获 -> 冒泡)
  • react17废弃了事件池

4. React合成事件伪代码分析

首先,构造一段dom结构,再分别打印原生事件和模拟的react事件的触发顺序

<body>
	<div id="root">
		<div class="parent">
			<div class="child">
				点击
			</div>
		</div>
	</div>
</body>
    document.getElementsByClassName('parent')[0].addEventListener('click',()=>{
		console.log("原生捕获:parent")
	},true)

	document.getElementsByClassName('child')[0].addEventListener('click',()=>{
		console.log("原生捕获:child")
	},true)

	document.getElementsByClassName('parent')[0].addEventListener('click',()=>{
		console.log("原生冒泡:parent")
	})

	document.getElementsByClassName('child')[0].addEventListener('click',()=>{
		console.log("原生冒泡:child")
	})

	document.getElementsByClassName('parent')[0].onClick =  function(){
		console.log("react冒泡:parent")
	}

	document.getElementsByClassName('child')[0].onClick = function(){
		console.log("react冒泡:child");
	}


	document.getElementsByClassName('parent')[0].onClickCapture =  function(){
		console.log("react捕获:parent")
	}

	document.getElementsByClassName('child')[0].onClickCapture = function(){
		console.log("react捕获:child");
	}

 5. react16合成事件伪代码模拟

const dispatchEvent = (e)=>{
	let paths = []
	let current = e.target;
	while(current){
		paths.push(current);
		current = current.parentNode;
	}

	// 模拟捕获与冒泡

	for(let i= paths.length - 1;i>=0;i--){
		let handler = paths[i].onClickCapture;
		handler && handler();
	}

	for (var i = 0; i < paths.length; i++) {
		let handler = paths[i].onClick;
		handler && handler();
	}
}

document.getElementById('document').addEventListener('click',dispatchEvent);

react16时以上代码的打印顺序是: 

原生捕获:parent
原生捕获:child
原生冒泡:child
原生冒泡:parent
react捕获:parent
react捕获:child
react冒泡:child
react冒泡:parent

6. React17合成事件伪代码模拟

    const dispatchEvent = (e,useCapture)=>{
		let paths = []
		let current = e.target;
		while(current){
			paths.push(current);
			current = current.parentNode;
		}

		// 模拟捕获与冒泡

		if(useCapture){
			for(let i= paths.length - 1;i>=0;i--){
				let handler = paths[i].onClickCapture;
				handler && handler();
			}
		}else{
			for (var i = 0; i < paths.length; i++) {
				let handler = paths[i].onClick;
				handler && handler();
			}
		}
		
	}

	document.getElementById('root').addEventListener('click',(e)=>dispatchEvent(e,true),true);
	document.getElementById('root').addEventListener('click',(e)=>dispatchEvent(e,false));

react17时以上代码的打印顺序是:

react捕获:parent
react捕获:child
原生捕获:parent
原生捕获:child
原生冒泡:child
原生冒泡:parent
react冒泡:child
react冒泡:parent

7. 结语

React合成事件应用了事件委托,并且在16版本与17版本合成事件有很多差异

实际上,react的合成事件涉及到很多内容,实现非常复杂,这里只是简单模拟,以对合成事件有一些了解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值