最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • React 合成事件和 DOM 原生事件混用

    正文概述 掘金(马赛克)   2020-12-08   457

    昨天面试被问到一个元素同时绑定 React 合成事件和 DOM 原生事件的问题。首先来说,我在项目中真的没遇到过这样的场景,我也没想到要用的那些场景中。

    React 合成事件

    先来说说 React 合成事件是怎么回事。

    React 合成事件是如何工作的

    React 的事件系统沿袭了事件委托的思想。在 React 中,除了少数特殊的不可冒泡的事件(比如媒体类型的事件)无法被事件系统处理外,绝大部分的事件都不会被绑定在具体的元素上,而是统一被绑定在页面的 document 上。当事件在具体的 DOM 节点上被触发后,最终都会冒泡到 document 上,document 上所绑定的统一事件处理程序会将事件分发到具体的组件实例。在分发事件之前,React 首先会对事件进行包装,把原生 DOM 事件包装成合成事件。

    为什么 React 要自己定义合成事件

    首先一定要说的,也是 React 官方说明过的一点是:合成事件符合W3C规范,在底层抹平了不同浏览器的差异,在上层面向开发者暴露统一的、稳定的、与 DOM 原生事件相同的事件接口。开发者们由此便不必再关注烦琐的底层兼容问题,可以专注于业务逻辑的开发。

    此外,自研事件系统使 React 牢牢把握住了事件处理的主动权。

    在 React 中使用原生事件

    其实和原生没什么区别,就是获取 DOM 的方式可能不太一样。

    import React, { useEffect, useRef } from 'react'
    
    function Demo() {
      const dome = useRef(null)
    
      useEffect(() => {
        dome.current.addEventListener('click', clickDOMButton, false)
      }, [])
    
      function clickDOMButton() {
        console.log('DOM event')
      }
    
      return (
        <div>
          <button ref={dome}>
            按钮
          </button>
        </div>
      )
    }
    
    export default Demo
    

    或者可以自己写一个工具函数,抹平浏览器间的差异,然后在组件中直接调用。

    export const addEventListener = (
      target,
      type,
      listener,
      useCapture = false
    ) => {
      if (target.addEventListener) {
        target.addEventListener(type, listener, useCapture)
      } else if (target.attachEvent) {
        target.attachEvent(`on${type}`, listener)
      } else {
        target[`on${type}`] = listener
      }
    }
    

    二者混合使用

    执行顺序

    import React, { useEffect, useRef } from 'react'
    
    function Demo() {
      const dome = useRef(null)
    
      useEffect(() => {
        addEventListener(dome.current, 'click', clickDOMButton, false)
      }, [])
    
      function clickDOMButton() {
        console.log('DOM event')
      }
    
      function clickReactButton() {
        console.log('React event')
      }
    
      return (
        <div>
          <button ref={dome} onClick={clickReactButton}>
            按钮
          </button>
        </div>
      )
    }
    
    export default Demo
    

    上边代码对 button 元素分别绑定了两种事件,当点击时打印的结果为:

    DOM event
    React event
    

    原因也很简单,当点击 button 时,原生事件直接就触发了,而合成事件要冒泡至 document 之后,才会去触发。

    阻止冒泡

    React 官网有这样一句话,意思是从事件处理程序返回 false 将不再停止事件传播,而是应适当地手动触发e.stopPropagation()e.preventDefault()

    那我们就来试一试吧,先在合成事件中阻止冒泡。

    import React, { useEffect, useRef } from 'react'
    
    function Demo() {
      const wrapper = useRef(null)
      const dome = useRef(null)
    
      useEffect(() => {
        addEventListener(wrapper.current, 'click', clickDOMWrapper, false)
        addEventListener(dome.current, 'click', clickDOMButton, false)
      }, [])
    
      function clickDOMWrapper() {
        console.log('wrapper DOM event')
      }
    
      function clickDOMButton() {
        console.log('button DOM event')
      }
    
      function clickReactWrapper() {
        console.log('wrapper React event')
      }
    
      function clickReactButton(e) {
        e.stopPropagation()
        console.log('button React event')
      }
      
      return (
        <div ref={wrapper} onClick={clickReactWrapper}>
          <button ref={dome} onClick={clickReactButton}>
            按钮
          </button>
        </div>
      )
    }
    
    export default Demo
    

    结果打印了这些:

    button DOM event
    wrapper DOM event
    button React event
    

    这证明了合成事件不会影响到原生事件。因为 React 给合成事件封装的 stopPropagation 函数在调用时给自己加了个 isPropagationStopped 的标记位来确定后续监听器是否执行。

    那如果在原生事件中阻止冒泡呢?上边的例子改为:

    function clickDOMWrapper() {
      console.log('wrapper DOM event')
    }
    
    function clickDOMButton(e) {
      e.stopPropagation()
      console.log('button DOM event')
    }
    
    function clickReactWrapper() {
      console.log('wrapper React event')
    }
    
    function clickReactButton() {
      console.log('button React event')
    }
    

    结果只打印了 button DOM event ,那就意味着在原生事件中使用 e.stopPropagation() 会阻止合成事件的执行。因为原生事件中使用 e.stopPropagation() 后,事件不会冒泡的 document,所以也就触发不了 document 上绑定的合成事件了。

    nativeEvent

    这里还有一个问题,就是当你需要访问原生事件对象时,可以通过合成事件对象的 e.nativeEvent 属性获取到它。但他可能和我们想象的不太一样。

    当我们使用 e.nativeEvent.stopPropagation() 试图去阻止冒泡时,不但不能阻止原生事件的冒泡,连合成事件的冒泡也不能阻止了。执行这段代码的时候,原生事件早就执行完了,而又没有去阻止合成事件的冒泡,也不知道应该在什么情况下使用。

    结论

    • React 合成事件和 DOM 原生事件混用,先执行原生事件,再去执行合成事件
    • 原生事件中使用 e.stopPropagation() 会阻止合成事件的执行,但在合成事件中使用 e.stopPropagation() 却不会阻止原生事件的执行。

    起源地下载网 » React 合成事件和 DOM 原生事件混用

    常见问题FAQ

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

    发表评论

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

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

    联系作者

    请选择支付方式

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