我们知道,React在内部对事件做了统一的处理,合成事件是一个比较大的概念

为什么要有合成事件

  1. 在传统的事件里,不同的浏览器需要兼容不同的写法,在合成事件中React提供统一的事件对象,抹平了浏览器的兼容性差异
  2. React通过root根元素顶层监听的形式,通过事件委托的方式来统一管理所有的事件,可以在事件上区分事件优先级,优化用户体验
事件委托

事件委托的意思就是可以通过给父元素绑定事件委托,通过事件对象的target属性可以获取到当前触发目标阶段的dom元素,来进行统一管理

比如写原生dom循环渲染的时候,我们要给每一个子元素都添加dom事件,这种情况最简单的方式就是通过事件委托在父元素做一次委托,通过target属性判断区分做不同的操作

事件监听

事件监听主要用到了addEventListener这个函数,具体怎么用可以点击进行查看 事件监听和事件绑定的最大区别就是事件监听可以给一个事件监听多个函数操作,而事件绑定只有一次

// 可以监听多个,不会被覆盖 eventTarget.addEventListener('click', () => {}); eventTarget.addEventListener('click', () => {}); eventTarget.onclick = function () {}; eventTarget.onclick = function () {}; // 第二个会把第一个覆盖

事件执行顺序

<div> <span>点我</span> </div>

当我们点击span标签的时候会经过这么三个过程,在路径内的元素绑定的事件都会进行触发

捕获阶段 => 目标阶段 => 冒泡阶段

合成事件

在看之前先看一下这几个问题

  • 原生事件和合成事件的执行顺序是什么?
  • 合成事件在什么阶段下会被执行?
  • 阻止原生事件的冒泡,会影响到合成事件的执行吗?
  • 阻止合成事件的冒泡,会影响到原生事件的执行吗?

事件传播 

事件处理函数还将捕获任何来自子组件的事件。通常,我们会说事件会沿着树向上“冒泡”或“传播”:它从事件发生的地方开始,然后沿着树向上传播。

下面这个 <div> 包含两个按钮。<div> 和每个按钮都有自己的 onClick 处理函数。你认为点击按钮时会触发哪些处理函数?

如果你点击任一按钮,它自身的 onClick 将首先执行,然后父级 <div>onClick 会接着执行。因此会出现两条消息。如果你点击 toolbar 本身,将只有父级 <div>onClick 会执行。

陷阱

在 React 中所有事件都会传播,除了 onScroll,它仅适用于你附加到的 JSX 标签。

阻止传播 

事件处理函数接收一个 事件对象 作为唯一的参数。按照惯例,它通常被称为 e ,代表 “event”(事件)。你可以使用此对象来读取有关事件的信息。

这个事件对象还允许你阻止传播。如果你想阻止一个事件到达父组件,你需要像下面 Button 组件那样调用 e.stopPropagation()

当你点击按钮时:

  1. React 调用了传递给 <button> 的 onClick 处理函数。
  2. 定义在 Button 中的处理函数执行了如下操作:
    • 调用 e.stopPropagation(),阻止事件进一步冒泡。
    • 调用 onClick 函数,它是从 Toolbar 组件传递过来的 prop。
  3. 在 Toolbar 组件中定义的函数,显示按钮对应的 alert。
  4. 由于传播被阻止,父级 <div> 的 onClick 处理函数不会执行。

由于调用了 e.stopPropagation(),点击按钮现在将只显示一个 alert(来自 <button>),而并非两个(分别来自 <button> 和父级 toolbar <div>)。点击按钮与点击周围的 toolbar 不同,因此阻止传播对这个 UI 是有意义的。

深入探讨
捕获阶段事件 

收起

极少数情况下,你可能需要捕获子元素上的所有事件,即便它们阻止了传播。例如,你可能想对每次点击进行埋点记录,传播逻辑暂且不论。那么你可以通过在事件名称末尾添加 Capture 来实现这一点:


<div onClickCapture={() => { /* 这会首先执行 */ }}>

<button onClick={e => e.stopPropagation()} />

<button onClick={e => e.stopPropagation()} />

</div>

每个事件分三个阶段传播:

  1. 它向下传播,调用所有的 onClickCapture 处理函数。
  2. 它执行被点击元素的 onClick 处理函数。
  3. 它向上传播,调用所有的 onClick 处理函数。

捕获事件对于路由或数据分析之类的代码很有用,但你可能不会在应用程序代码中使用它们。

传递处理函数作为事件传播的替代方案 

注意,此处的点击事件处理函数先执行了一行代码,然后调用了父组件传递的 onClick prop:

function Button({ onClick, children }) {

return (

<button onClick={e => {

e.stopPropagation();

onClick();

}}>

{children}

</button>

);

}

你也可以在调用父元素 onClick 函数之前,向这个处理函数添加更多代码。此模式是事件传播的另一种 替代方案 。它让子组件处理事件,同时也让父组件指定一些额外的行为。与事件传播不同,它并非自动。但使用这种模式的好处是你可以清楚地追踪因某个事件的触发而执行的整条代码链。

如果你依赖于事件传播,而且很难追踪哪些处理程序在执行,及其执行的原因,可以尝试这种方法。

阻止默认行为 

某些浏览器事件具有与事件相关联的默认行为。例如,点击 <form> 表单内部的按钮会触发表单提交事件,默认情况下将重新加载整个页面:

不要混淆 e.stopPropagation()e.preventDefault()。它们都很有用,但二者并不相关:

事件处理函数可以包含副作用吗? 

当然可以!事件处理函数是执行副作用的最佳位置。

与渲染函数不同,事件处理函数不需要是 纯函数,因此它是用来 更改 某些值的绝佳位置。例如,更改输入框的值以响应键入,或者更改列表以响应按钮的触发。但是,为了更改某些信息,你首先需要某种方式存储它。在 React 中,这是通过 state(组件的记忆) 来完成的。你将在下一章节了解所有相关信息。

摘要

  • 你可以通过将函数作为 prop 传递给元素如 <button> 来处理事件。
  • 必须传递事件处理函数,而非函数调用! onClick={handleClick} ,不是 onClick={handleClick()}
  • 你可以单独或者内联定义事件处理函数。
  • 事件处理函数在组件内部定义,所以它们可以访问 props。
  • 你可以在父组件中定义一个事件处理函数,并将其作为 prop 传递给子组件。
  • 你可以根据特定于应用程序的名称定义事件处理函数的 prop。
  • 事件会向上传播。通过事件的第一个参数调用 e.stopPropagation() 来防止这种情况。
  • 事件可能具有不需要的浏览器默认行为。调用 e.preventDefault() 来阻止这种情况。
  • 从子组件显式调用事件处理函数 prop 是事件传播的另一种优秀替代方案。

总结

  • 16版本先执行原生事件,当冒泡到document时,统一执行合成事件,
  • 17版本在原生事件执行前先执行合成事件捕获阶段,原生事件执行完毕执行冒泡阶段的合成事件,通过根节点来管理所有的事件

原生的阻止事件流会阻断合成事件的执行,合成事件阻止后也会影响到后续的原生执行

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐