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 相关文章推荐
fckeditor 获取文本框值的实现代码
Feb 09 Javascript
JS 实现获取打开一个界面中输入的值
Mar 19 Javascript
js获取location.href的参数实例代码
Aug 02 Javascript
javascript实现当前页导航激活的方法
Feb 27 Javascript
z-blog SyntaxHighlighter 长代码无法换行解决办法(基于jquery)
Nov 18 Javascript
详解js界面跳转与值传递
Nov 22 Javascript
原生js实现可拖拽效果
Feb 28 Javascript
vue-image-crop基于Vue的移动端图片裁剪组件示例
Aug 28 Javascript
微信小程序之判断页面滚动方向的示例代码
Aug 30 Javascript
jQuery实现基本淡入淡出效果的方法详解
Sep 05 jQuery
十分钟教你上手ES2020新特性
Feb 12 Javascript
vue-cli3.x配置全局的scss的时候报错问题及解决
Apr 30 Vue.js
vue实现可拖拽的dialog弹框
vue如何批量引入组件、注册和使用详解
JavaScript继承的三种方法实例
May 12 #Javascript
canvas绘制折线路径动画实现
JavaScript原始值与包装对象的详细介绍
May 11 #Javascript
vue组件的路由高亮问题解决方法
原生Js 实现的简单无缝滚动轮播图的示例代码
May 10 #Javascript
You might like
PHP exif扩展方法开启详解
2014/07/28 PHP
cakephp打印sql语句的方法
2015/02/13 PHP
Linux系统下php获得系统分区信息的方法
2015/03/30 PHP
完美利用Yii2微信后台开发的系列总结
2016/07/18 PHP
laravel框架创建授权策略实例分析
2019/11/22 PHP
ThinkPHP5.1+Ajax实现的无刷新分页功能示例
2020/02/10 PHP
通过继承IHttpHandle实现JS插件的组织与管理
2010/07/13 Javascript
关于JavaScript中var声明变量作用域的推断
2010/12/16 Javascript
映彩衣的js随笔(js图片切换效果)
2011/07/31 Javascript
jquery图片不完全按比例自动缩小的简单代码
2013/07/29 Javascript
JS Date函数整理方便使用
2013/10/23 Javascript
利用JS判断字符串是否含有数字与特殊字符的方法小结
2016/11/25 Javascript
JavaScript实现图片瀑布流和底部刷新
2017/01/02 Javascript
Node连接mysql数据库方法介绍
2017/02/07 Javascript
Angular如何引入第三方库的方法详解
2017/07/13 Javascript
vue中的event bus非父子组件通信解析
2017/10/27 Javascript
element-ui如何防止重复提交的方法步骤
2019/12/09 Javascript
javascript事件循环event loop的简单模型解释与应用分析
2020/03/14 Javascript
解决vue动态路由异步加载import组件,加载不到module的问题
2020/07/26 Javascript
[01:48]DOTA2 2015国际邀请赛中国区预选赛第二日战报
2015/05/27 DOTA
python使用mailbox打印电子邮件的方法
2015/04/30 Python
Python如何实现守护进程的方法示例
2017/02/08 Python
python中in在list和dict中查找效率的对比分析
2018/05/04 Python
Python爬虫实现的根据分类爬取豆瓣电影信息功能示例
2019/09/15 Python
Python 项目转化为so文件实例
2019/12/23 Python
Python直接赋值及深浅拷贝原理详解
2020/09/05 Python
Hotels.com日本:国外和海外住宿,酒店预订
2019/12/13 全球购物
应聘自荐书
2013/10/08 职场文书
继承公证书
2014/04/09 职场文书
初中英语演讲稿
2014/04/29 职场文书
公司会议策划方案
2014/05/17 职场文书
大学生心理活动总结
2014/07/04 职场文书
有关九一八事变的演讲稿
2014/09/14 职场文书
2015年清明节活动总结
2015/02/09 职场文书
市场部岗位职责范本
2015/04/15 职场文书
2015年公路养护工作总结
2015/05/13 职场文书