React 合成事件和 DOM 原生事件混用

昨天面试被问到一个元素同时绑定 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 (



按钮


)
}

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 (



按钮


)
}

export default Demo
复制代码

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

DOM event
React event
复制代码

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

Note:

As of v0.14, returning false from an event handler will no longer stop event propagation. Instead, e.stopPropagation() or e.preventDefault() should be triggered manually, as appropriate.

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 (



按钮


)
}

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() 却不会阻止原生事件的执行。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值