最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 从源码解析react合成事件如何阻止事件传播

    正文概述 掘金(若沉)   2021-01-19   734

    一、前言

      最近一直在看react、reactDom源码,深深被Facebook这群大佬所折服。在这个过程中,不仅是学习了react源码,更是对js有了全新的认识,以至于会产生JavaScript还可以这样写的感慨。
      另外,为加深对react的理解,也写了一些文章去记录一下react fiber概念及原理、react fiber如何生成、react 引入fiber之后的diff算法、react 源码之render阶段 。
      然后最近去面了一家公司,面试官问了我一个问题,react如何阻止事件传播,然后我迅速的回忆了一下,发现自己还没有去看这部分源码。作为一个佛系的人,我给他的回答是我不是很清楚,需要去看一下源码。面试结束后,为了解惑,我迅速的去撸了一边这块的源码,下面内容就是介绍reactDom是如何实现一套事件系统的。

    二、原生js如何阻止事件传播

      在去读reactDom事件源码之前,还是要先回顾一下原生js事件循环的内容,老司机可以跳过,直接看下一章节

    什么是事件循环

    事件循环包含了事件传播与事件监听
    事件传播简单的说就是当页面中触发一个事件,比如鼠标点击click事件,这个事件会沿着dom传播。比如下面这个dom结构

    <div>
      <p>
        <span><span/>
      <p/>
    <div/>
    

    触发click事件会沿着div --> p --> span --> p --> div传播,我们把这样一个现象称为事件传播。
    而当我们在span上注册了一个click事件监听器,如下

    document.getElementsByTagname('span')[0].addEventListener('click', function callBack(e) {
      console.log('点击了span')
    })
    

    那么当事件传播到span时,就会触发我们传入的callBack函数,而这个span我们称之为事件传播中的target。
    上面例子中,span是最后个子节点,我们以这个span为节点把事件传播分为两个阶段

    1. 第一个阶段是从div --> p --> span,称之为捕获阶段
    2. 第二个阶段是从span --> p --> div,称之为冒泡阶段

    我们知道,如果再p节点注册一个click事件监听器,这个监听器难道会触发两次回调函数吗?显然不会,js只会在其中一个阶段触发监听器的回调函数,那么是哪一个阶段呢?这就依靠addEventListener的第三个参数,它可以接受一个布尔值useCapture,表示监听器是否在捕获阶段触发,也可以接受一个对象option。这里按一个布尔值来举例。当useCapture为true时,p的监听器会在捕获阶段触发,当useCapture为false时会在冒泡阶段触发。

    如何阻止事件传播

    现在有这么一个需求,还是上面的例子,我们在span和p上都注册了click事件监听器,且useCapture都为false,即在冒泡阶段触发。如下

    document.getElementsByTagname('span')[0].addEventListener('click', function callBack(e) {
      console.log('点击了span')
    }, false)
    document.getElementsByTagname('p')[0].addEventListener('click', function callBack(e) {
      console.log('点击了p')
    }, false)
    

    如果我们点击span,我们只想触发span的监听器,而不像触发p的监听器,这如何处理。js也提供了API,监听器的回调函数接收一个event参数,表示传播中的事件实例,当中有stopPropagation和stopImmediatePropagation两个方法,这两个api可以阻止事件继续向上或者向下传播。

    e.stopPropagation()和e.stopImmediatePropagation()的区别

    那么stopPropagation和stopImmediatePropagation有什么区别呢?先看一个例子

    document.getElementsByTagname('span')[0].addEventListener('click', function callBack1(e) {
      console.log('点击了span1')
    }, false)
    document.getElementsByTagname('span')[0].addEventListener('click', function callBack2(e) {
      console.log('点击了span2')
    }, false)
    document.getElementsByTagname('span')[0].addEventListener('click', function callBack3(e) {
      console.log('点击了span1')
    }, false)
    document.getElementsByTagname('p')[0].addEventListener('click', function callBack4(e) {
      console.log('点击了p')
    }, false)
    

    例子中我们在span上注册了三个click事件监听器,如果再callback2中调用了e.stopPropagation(),callBack1,callBack2,callBack3都会执行,而callBack4由于事件传播被阻止没有执行。
    如果我们调用的是e.stopImmediatePropagation(),那么callBack3和callBack4不会执行。也就是说,stopImmediatePropagation会阻止当前dom注册的监听器的触发,而且是在之后注册的监听器,及例子中callBack3在callBack2之后注册。

    三、reactDom事件系统如何实现

    react在js事件循环的基础上做了一套自己的事件系统,目的就在于解决不同浏览器之间的兼容问题,接下来我们看看react 事件是如何实现的。

    在根节点注册事件

    首先,reactDom在创建整个应用的根节点的时候会去调用listenToAllSupportedEvents这个方法 从源码解析react合成事件如何阻止事件传播 其中allNativeEvents就是一个包含浏览器支持的所有事件的数组,它会遍历这个数组,根据事件类型调用listenToNativeEvent这个函数。区别就在于第二参数,代表着捕获还是冒泡。而注册事件的节点就是应用的根节点,即reactDom.render()中传入的第二个参数。
    (需要注意的是同时支持冒泡和捕获的事件会注册两个监听器)
    另外,可以发现selectionChange这个事件时注册在document上的
    从源码解析react合成事件如何阻止事件传播 listenToNativeEvent最终会addEventListener去注册相应的事件监听器。那么重点就在于注册监听器的回调函数listener是啥。代码中可以看到listener是由createEventListenerWrapperWithPriority方法返回的 从源码解析react合成事件如何阻止事件传播 那么看下createEventListenerWrapperWithPriority 从源码解析react合成事件如何阻止事件传播 react会根据事件类型,返回不同的listener。但是最终这个listener调用的函数都是一样的,那就是dispatchEventsForPlugins 从源码解析react合成事件如何阻止事件传播 可以非常清晰的看到,这里用了发布订阅模式,通过extractEvent$5订阅消息,processDispatchQueue发布消息

    根节点注册事件回调函数

    1.订阅

    最终,react运行下面的代码 从源码解析react合成事件如何阻止事件传播 它会想dispatchQueue队列中推入一个对象,包含event和listenners属性,分别看一下这两个属性

    1. event是SyntheticEnentCtor这个类的实例(根据不同类型事件,有着不同的SyntheticEnentCtor),我们以click事件为例

    从源码解析react合成事件如何阻止事件传播 可以看到,这个event其实就是一个类似于js event的一个对象,它有一个指针指向原生event对象。 2. listeners是一个队列,里面记录了此次触发事件需要执行的回调函数 从源码解析react合成事件如何阻止事件传播 可以看到react会从此次事件循环中的target对应的fiber一直向上遍历,如果是capture(捕获)事件,则会向listeners前面插入监听函数(captureListener)。否则在listeners队尾插入监听函数(bubbkeListener)。这也就遵循了先捕获再冒泡的规则
    而这里的captureListener和bubbkeListener就是我们在jsx中写的onClick对应的函数

    2.发布

    从以上内容可知,react会在初始化时向根节点注册事件,当触发某个事件时会调用监听器的回调函数,然后从目标fiber向上遍历收集此次事件需要执行的回到函数,推入到dispatchQueue队列等待执行。 从源码解析react合成事件如何阻止事件传播 dispatchQueue是已经按照先捕获后冒泡的规则排列的队列,所以只需要循环执行就行。

    3.react如何阻止合成事件传播

    那么如何阻止react事件传播呢?在订阅阶段,react会实例化一个event,包含了stopPropagation函数,这个函数主要做了两件事:

    1. 阻止原生事件(event.nativeEvent)事件传播
    2. 将isPropagationStopped标记为true

    在发布阶段去循环执行dispatchQueue时会与一个判断条件,如果isPropagationStopped是true,则直接退出循环,也就达到了中断事件传播的效果。 从源码解析react合成事件如何阻止事件传播

    四、reactDom事件和原生事件混合场景

    写了这么多,来总结一下react事件系统

    1. 向react应用根节点注册支持的所有事件
    2. 当触发事件时,会捕获或者冒泡到根节点,调用回调函数
    3. 收集此次需要执行的react事件对应的回调函数
    4. 执行此次需要执行的react事件对应的回调函数
    5. 原生事件继续向上冒泡或向下捕获

    也就是说在我们调用react事件的stopPropagation时,事件已经冒泡或者捕获到了根节点,比如一下例子 从源码解析react合成事件如何阻止事件传播 当我们点击span标签,又不想冒泡到p,此时在span的onClick调用stopPropagation时会发现,先打印出了parent,后打印出了child。也就是说在调用e.stopPropagation时,事件已经冒泡到了根节点,p的监听器的回调函数也早已执行。那么如何阻止呢?

    1. 将事件通过委托的方式注册到根节点的祖先节点,比如document.body、document
    2. 也可以将事件通过委托的方式注册到根节点,通过stopImmediatePropagation阻止事件传播,前提是事件监听在组件渲染完成后

    起源地 » 从源码解析react合成事件如何阻止事件传播

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元