react合成事件与原生事件的相关理解


Posted in Javascript onMay 13, 2021

1. 原生事件

原生事件就是js的原生事件,如通过document.addEventListener来设置的监听事件。

在react中即使有自己的一套事件机制(见下面合成事件),但有时候的业务场景我们仍然需要使用原生事件。比如我们封装一个Modal弹窗组件,需要在点击非弹窗区域时关掉弹窗,此时我们只能针对document进行原生点击事件监听。

由于原生事件需要绑定在真实DOM上,所以一般是在componentDidMount阶段或者组件/元素的ref的函数执行阶段进行绑定操作,并且注意要在componentWillUnmount阶段进行解绑操作以避免内存泄漏。

2. 合成事件

React有自己的一套事件机制,它重新封装了绝大部分的原生事件。合成事件采用了事件池,这样做可以大大节省内存,而不会频繁的创建和销毁事件对象。

在React中,如果需要绑定事件,我们常常在jsx中这么写:

handleClick(){
}
<div onClick={this.handleClick.bind(this)}>
	react事件
</div>

大致原理:

React并不是将click事件绑在该div的真实DOM上,而是在document处监听所有支持的事件,当事件发生并冒泡至document处时,React将事件内容封装并交由真正的处理函数运行。

以上面的代码为例,整个事件生命周期示意如下:

react合成事件与原生事件的相关理解

合成事件的一些特点总结:

  • React 上注册的事件最终会绑定在document这个 DOM 上,而不是 React 组件对应的 DOM(减少内存开销就是因为所有的事件都绑定在 document 上,其他节点没有绑定事件)
  • React 通过队列的形式,从触发的组件向父组件回溯,然后调用他们 JSX 中定义的 callback
  • React 通过对象池的形式管理合成事件对象的创建和销毁,减少了垃圾的生成和新对象内存的分配,提高了性能

了解react合成事件的大概原理后,方便我们解答下面一个问题:

为什么react事件需要手动绑定this

合成事件触发之后会冒泡一路到document的节点,然后开始分发document节点收集到的事件,这个时候react从事件触发的组件实例开始, 遍历虚拟dom树,从树上取下我们绑定的事件,收集起来,然后执行。举个例子:

class Test extends React.Component {
   fatherHandler =  function father() { /*...*/}
   childHander = function child() {/*...*/}

   render(){
     return (
       <div onClick={this.fatherHandler}>
         <span onClick={this.childHander}>
         </span>
       </div>
     );
   }

}

当事件触发以后react会把上面的事件处理函数放到一个数组里是这样的

[father, child]

最后,react只要遍历执行这个数组,就能执行所有需要执行的事件处理函数。这里react对函数进行了临时保存,这个时候执行的话,this自然就丢失了。

如果react保存顺便保存一下实例,还是可以做到,不需要你绑定this的,但是这样对于react来说代价太大了。

3. 原生与合成事件触发顺序

componentDidMount() {
    this.parent.addEventListener('click', (e) => {
      console.log('dom parent');
    })
    this.child.addEventListener('click', (e) => {
      console.log('dom child');
    })
    document.addEventListener('click', (e) => {
      console.log('document');
    })
  }

  childClick = (e) => {
    console.log('react child');
  }

  parentClick = (e) => {
    console.log('react parent');
  }

  render() {
    return (
      <div onClick={this.parentClick} ref={ref => this.parent = ref}>
        <div onClick={this.childClick} ref={ref => this.child = ref}>
          test
        </div>
      </div>)
  }

点击child中的test后,事件触发顺序如下:

react合成事件与原生事件的相关理解

结论:

无论是否是对于同一元素监听的同种类型事件,原生事件总是比合成事件先触发。这是由于上面我们说到的合成事件最终都会绑定到documnet DOM上导致的,当合成事件监听到后,总是冒泡到document才会真正触发。 而documnet DOM上监听的原生事件则总是最后触发

4. 合成事件和原生事件混用

react合成事件和原生事件最好不要混用。

原生事件中如果执行了stopPropagation(阻止冒泡)方法,则很容易导致其他同类型react合成事件失效。因为这样所有同级以及后代元素的合成事件和原生事件都将无法冒泡到document上。

而如果仅仅是合成事件中使用了e.stopPropagation(阻止冒泡)方法,则不会影响原生事件的冒泡

相关疑问:

我们知道React事件监听器中获得的入参并不是浏览器原生事件,原生事件可以通过e.nativeEvent来获取。通过这种方式,合成事件可以影响原生事件吗?

e.nativeEvent.stopPropagation

即使在react的合成事件中调用原生事件的阻止冒泡,实际作用是在DOM最外层阻止冒泡,并不符合预期。也就是说它最终只能控制当前监听的合成事件不会冒泡到document DOM的原生事件

e.nativeEvent.stopImmediatePropagation

该方法与上面的nativeEvent.stopPropagation有类似的功能,都可阻止当前监听的合成事件冒泡到document DOM的原生事件

stopImmediatePropagation常常在多个第三方库混用时,用来阻止多个事件监听器中的非必要执行。比如同一个元素的同种事件,设置了多个监听事件函数,则该方式可以控制监听函数只触发第一个

stopImmediatePropagation和stopPropagation本都是原生事件,但在React自己的事件体系中,重新封装了后者,却没有封装前者。导致在合成事件中只能手动调用nativeEvent.stopImmediatePropagation。

因为在React的合成事件机制中,一个组件只能绑定一个同类型的事件监听器(重复定义时,后面的监听器会覆盖之前的),所以合成事件无需去封装stopImmediatePropagation。

所以,在React的合成事件中,e.nativeEvent.stopPropagation和e.nativeEvent.stopImmediatePropagation实际的作用是等价的

此外,由于事件绑定的顺序问题,需要注意,如果是在react-dom.js加载前绑定的document原生事件,stopImmediatePropagation也是无法阻止的。

以上就是react合成事件与原生事件的相关理解的详细内容,更多关于react合成事件与原生事件的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
在html页面中包含共享页面的方法
Oct 24 Javascript
基于jQuery UI CSS Framework开发Widget的经验
Aug 21 Javascript
使用jQuery的将桌面应用程序引入浏览器
Nov 19 Javascript
Jquery实现搜索框提示功能示例代码
Aug 13 Javascript
JS 打印界面的CSS居中代码适用所有浏览器
Mar 19 Javascript
js实现带缓冲效果的仿QQ面板折叠菜单代码
Sep 06 Javascript
js和C# 时间日期格式转换的简单实例
May 28 Javascript
详解XMLHttpRequest(一)同步请求和异步请求
Sep 14 Javascript
JS短信验证码倒计时功能的实现(没有验证码,只有倒计时)
Oct 27 Javascript
利用node.js+mongodb如何搭建一个简单登录注册的功能详解
Jul 30 Javascript
vue 项目打包时样式及背景图片路径找不到的解决方式
Nov 12 Javascript
three.js 如何制作魔方
Jul 31 Javascript
vue实现可拖拽的dialog弹框
vue如何批量引入组件、注册和使用详解
JavaScript继承的三种方法实例
May 12 #Javascript
canvas绘制折线路径动画实现
JavaScript原始值与包装对象的详细介绍
May 11 #Javascript
vue组件的路由高亮问题解决方法
原生Js 实现的简单无缝滚动轮播图的示例代码
May 10 #Javascript
You might like
模拟SQLSERVER的两个函数:dateadd(),datediff()
2006/10/09 PHP
PHP5.4中json_encode中文转码的变化小结
2013/01/30 PHP
php并发对MYSQL造成压力的解决方法
2013/02/21 PHP
ThinkPHP5.0 图片上传生成缩略图实例代码说明
2018/06/20 PHP
js window.open弹出新的网页窗口
2014/01/16 Javascript
JQUERY实现网页右下角固定位置展开关闭特效的方法
2015/07/27 Javascript
设计模式中的facade外观模式在JavaScript开发中的运用
2016/05/18 Javascript
jQuery中设置form表单中action值的实现方法
2016/05/25 Javascript
jQuery Checkbox 全选 反选的简单实例
2016/11/29 Javascript
JavaScript基础之this详解
2017/06/04 Javascript
jQuery代码优化方法总结
2018/01/29 jQuery
jQuery 实现倒计时天,时,分,秒功能
2018/07/31 jQuery
Vue实现星级评价效果实例详解
2019/12/30 Javascript
用实例详解Python中的Django框架中prefetch_related()函数对数据库查询的优化
2015/04/01 Python
python类继承用法实例分析
2015/05/27 Python
Python自动扫雷实现方法
2015/07/25 Python
解决项目pycharm能运行,在终端却无法运行的问题
2019/01/19 Python
浅谈Python3实现两个矩形的交并比(IoU)
2020/01/18 Python
Python基于staticmethod装饰器标示静态方法
2020/10/17 Python
HTML5实现无刷新修改URL的方法
2019/11/14 HTML / CSS
html5的pushstate以及监听浏览器返回事件的实现
2020/08/11 HTML / CSS
国际鲜花速递专家:Floraqueen
2016/11/24 全球购物
Lookfantastic日本官网:英国知名护肤、化妆品和头发护理购物网站
2018/04/21 全球购物
英国厨房与餐具用品为主的设计品牌:Joseph Joseph
2018/04/26 全球购物
Rhone官方网站:男士运动服装、健身服装和高级运动服
2019/05/01 全球购物
用JAVA SOCKET编程,读服务器几个字符,再写入本地显示
2012/11/25 面试题
业务员岗位职责范本
2013/12/15 职场文书
幼儿园教师培训制度
2014/01/16 职场文书
马云北大演讲完整版:真心话,什么才是阿里的核心竞争力?
2014/04/04 职场文书
毕业论文指导教师评语
2014/12/30 职场文书
北京青年观后感
2015/06/15 职场文书
七一慰问简报
2015/07/20 职场文书
python基础学习之递归函数知识总结
2021/05/26 Python
详解运行Python的神器Jupyter Notebook
2021/06/03 Python
Springboot使用Spring Data JPA实现数据库操作
2021/06/30 Java/Android
B站评分公认最好看的动漫,你的名字评分9.9,第六备受喜欢
2022/03/18 日漫