React系列之合成事件与事件处理机制

news/2024/6/15 2:27:15 标签: react.js, javascript, 前端

文章目录

  • React事件处理机制
    • 原生事件的事件机制
      • 事件代理(事件委托)
    • 合成事件
      • 使用合成事件目的
      • 合成事件原生事件区别
      • 事件池
    • 原生事件和React事件的执行顺序
      • e.stopPropagation()
    • React17事件机制的修改

React事件处理机制

react 事件机制基本理解:react 基于浏览器的事件机制,自身实现了一套自己的事件机制,包括事件注册、事件的合成、事件冒泡、事件派发等。
react 的所有事件并没有绑定到具体的dom节点上而是绑定在了 document 上,然后由统一的事件处理程序来处理,同时也是基于浏览器的事件机制(冒泡),所有节点的事件都会在 document 上触发。

React 的事件处理机制可以分为两个阶段:

  • 初始化渲染时在 root 节点上注册原生事件;
  • 原生事件触发时模拟、目标和冒泡阶段派发合成事件。
    通过这种机制,冒泡的原生事件类型最多在 root 节点上注册一次,节省内存开销。且 React 为不同类型的事件定义了不同的处理优先级,从而让用户代码及时响应高优先级的用户交互,提升用户体验。

合成事件是 React 模拟原生 DOM 事件所有能力的一个事件对象,它可以兼容所有浏览器,也拥有和浏览器原生事件相同的接口。

原生事件的事件机制

在这里插入图片描述
事件流向分为三个阶段:捕获阶段、目标阶段、冒泡阶段。
捕获阶段是指事件响应从最外层的Window开始逐层向内层推进,直到具体的目标元素。
目标阶段是触发事件的目标元素。
冒泡阶段与捕获阶段相反,事件的响应是从最底层开始一层层向外传递到最外层的Window。

我们可以通过element.addEventListener()设置一个元素的事件模型为冒泡事件还是捕获事件。
element.addEventListener(type, listener, useCapture)
type:监听的事件类型(例’click’)
listener:事件的回调函数
useCapture:默认值为false,表示元素事件模型为事件冒泡,设为true时为事件捕获。

在这里插入图片描述

事件代理(事件委托)

事件代理(也称为事件委托)是一种利用DOM事件机制,通过将事件处理程序添加到父元素而不是每个子元素上来提高性能的技术。当子元素上的事件触发时,事件会冒泡/捕获到父元素,然后由父元素上的事件处理程序进行处理。
例对于列表元素,我们希望将用户点击了哪个item打印出来,通常我们可以给每个item注册点击事件监听器,但通过事件代理,可以将多个事件监听器减少为一个,这样可以减少重复工作,也可以减少内存。还可以使新增元素动态绑定事件。

var items = document.getElementById('item-list');
//事件捕获实现事件代理
items.addEventListener('click', (e) => {console.log('捕获:click ',e.target.innerHTML)}, true);
//事件冒泡实现事件代理
items.addEventListener('click', (e) => {console.log('冒泡:click ',e.target.innerHTML)}, false);

事件代理既可以通过事件冒泡来实现,也可以通过事件捕获来实现。

合成事件

React 合成事件(SyntheticEvent)是 React 模拟原生 DOM 事件所有能力的一个事件对象,即浏览器原生事件的跨浏览器包装器。它根据 W3C 规范来定义合成事件,兼容所有浏览器,拥有与浏览器原生事件相同的接口。

即在react中,我们绑定的事件onClick等,并不是原生事件,而是由原生事件合成的React事件,比如 click 事件合成为 onClick 事件。比如 blur, change, input, keydown, keyup, 合成为 onChange。可以通过 e.nativeEvent 属性获取原生事件。

使用合成事件目的

  • 方便事件统一管理
  • 进行浏览器兼容,实现更好的跨平台
  • 避免垃圾回收:事件对象可能会被频繁创建和回收,因此 React 引入事件池,在事件池中获取或释放事件对象。即 React 事件对象不会被释放掉,而是存放进一个数组中,当事件触发,就从这个数组中弹出,避免频繁地去创建和销毁(垃圾回收) 。

合成事件原生事件区别

命名方式不同:原生事件命名为纯小写,合成事件命名用小驼峰。
事件处理函数写法不同:原生事件中函数为字符串,合成事件里为函数。
阻止默认行为方式不同:原生事件中可以返回 false 来阻止默认行为,合成事件里需要显式调用 e.preventDefault() 来阻止。

事件池

合成事件对象池,是 React 事件系统提供的一种性能优化方式。合成事件对象在事件池统一管理,不同类型的合成事件具有不同的事件池。
React 事件池仅支持在 React16 及更早版本中,在 React17 已经不使用事件池。

SyntheticEvent 对象会被放入池中统一管理。这意味着 SyntheticEvent 对象可以被复用,当所有事件处理函数被调用之后,其所有属性都会被置空。例如,以下代码是无效的:

function handleChange(e) {
  // This won't work because the event object gets reused.
  setTimeout(() => {
    console.log(e.target.value); // Too late!
  }, 100);
}

https://zh-hans.legacy.reactjs.org/docs/legacy-event-pooling.html

原生事件和React事件的执行顺序

React 所有事件都挂载在 document 对象上。当真实 DOM 元素触发事件,会冒泡到 document 对象后,再处理 React 事件。
所以会先执行原生事件,然后处理 React 事件,最后真正执行 document 上挂载的事件。

原生事件(阻止冒泡)会阻止合成事件的执行;合成事件(阻止冒泡)不会阻止原生事件的执行。

e.stopPropagation()

e.stopPropagation() 可以阻止合成事件在组件树中的冒泡传播。事件本身还都是在 document 上执行。不能阻止原生事件的冒泡传播。
原生事件中如果执行了 stopPropagation 方法,则会导致其他 React 事件失效。因为所有元素的事件将无法冒泡到document上。

React17事件机制的修改

  • 事件统一绑定container上,ReactDOM.render(app, container);而不是document上,这样好处是有利于微前端的,微前端一个前端系统中可能有多个应用,如果继续采取全部绑定在document上,那么可能多应用下会出现问题。比如如果你在一个 React 子应用的 React 事件中调用了 e.stopPropagation(),无法阻止事件冒泡到外部树,因为真实的事件早已传播到 document。
  • React17 对齐了浏览器原生标准。比如 onScroll 事件不再进行事件冒泡。onFocus 和 onBlur 使用原生 focusin, focusout 合成。
  • React17 取消事件池复用。“它并不会提高现代浏览器的性能,甚至还会使经验丰富的开发者一头雾水” -> React 在旧浏览器中重用了不同事件的事件对象,以提高性能,并将所有事件字段在它们之前设置为 null。在 React 16 及更早版本中,使用者必须调用 e.persist() 才能正确的使用该事件,或者正确读取需要的属性。

在这里插入图片描述

https://zh-hans.legacy.reactjs.org/blog/2020/08/10/react-v17-rc.html


http://www.niftyadmin.cn/n/5461666.html

相关文章

vuejs 设计与实现(嵌套的effect与effect栈)

effect 是可以发生嵌套的 effect(function effectFn1(){effect(function effectFn2(){/* .... */})/* .... */ })如下代码嵌套执行 // 全局变量 let temp1, temp2// effectFn1 嵌套了 effectFn2 effect(function effectFn1(){console.log(effectFn1 执行)effect(function eff…

抽象类和接口的简单认识

目录 一、抽象类 1.什么是抽象类 2.抽象类的注意事项 3.抽象类与普通类的对比 二、接口 1.接口的简单使用 2.接口的特性 3.接口的使用案例 4.接口和抽象类的异同 一、抽象类 所谓抽象类,就是更加抽象的类,也就是说,这个类不能具体描…

C++11---右值引用(深度讲解)

简要介绍 右值引用是C11的新特性,无论左值引用还是右值引用,都是在给对象取别名 什么是左值 什么是右值 1.左值,左值引用 左值是一个数据的表达式(例如变量或者解引用后的指针),我们可以对其进行取地址和修改赋值,左值可以出现在赋值符号的左边,而右值不能出现在…

解决多模块项目报错,找不到程序包

本周,我遇到了一个常见的错误——“找不到程序包”。这个错误是由于模块间的依赖关系没有正确配置导致的。经过一系列的尝试和排查,我最终找到了解决问题的方法。下面,我将详细记录这次问题的处理过程,并总结其中的经验教训。 问…

C++2D原创我的世界1.00.3版本上市!!!

我很郁闷,为什么就是整不了昼夜交替啊喂!!!!!!!! 虽然这看上去很简单,但做起来要我命!!! 优化过后总共1312行&#xff0c…

Docker搭建LNMP环境实战(06):Docker及Docker-compose常用命令

Docker搭建LNMP环境实战(06):Docker及Docker-compose常用命令 此处列举了docker及docker-compose的常用命令,一方面可以做个了解,另一方面可以在需要的时候进行查阅。不一定要强行记忆,用多了就熟悉了。 1、…

LeetCode Python - 81. 搜索旋转排序数组 II

目录 题目描述解法运行结果 题目描述 已知存在一个按非降序排列的整数数组 nums &#xff0c;数组中的值不必互不相同。 在传递给函数之前&#xff0c;nums 在预先未知的某个下标 k&#xff08;0 < k < nums.length&#xff09;上进行了 旋转 &#xff0c;使数组变为 […

C语言函数调⽤应具备哪些条件?

一、问题 在主调函数中调⽤某函数之前应对该被调函数进⾏说明&#xff08;声明&#xff09;&#xff0c;这与使⽤变量之前要先进⾏变量声明是⼀样的。那么函数调⽤应具备哪些条件呢&#xff1f; 二、解答 在主调函数中对被调函数进⾏声明的⽬的是使编译系统知道被调函数返回值…