React事件机制 – 合成事件

2023-11-07

React事件机制是React重要的基础知识,也是面试过程中经常被提及的问题。本文将介绍React事件机制 - 合成事件的概念以及作用等知识点,本文将重点以React V17版本为准。
本文所用代码仓库

一、 概念介绍

React基于浏览器的事件机制自身实现了一套事件机制,包括事件注册、事件的合成、事件冒泡、事件派发等,在React中这套事件机制被称之为合成事件。
与原生事件直接在元素上注册的方式不同的是,React的合成事件不会直接绑定到目标DOM节点上,用事件委托机制,以队列的方式,从触发事件的组件向父组件回溯,直到Root节点。因此,React组件上声明的事件最终绑定到了Root对象(React17之前是Document)上。在Root节点,用一个统一的监听器去监听,这个监听器上保存着目标节点与事件对象的映射。当组件挂载或卸载时,只需在这个统一的事件监听器上插入或删除对应对象;当事件发生时(即Root上的事件处理函数被执行),在映射里按照冒泡或捕获的路径去组件中收集真正的事件处理函数,然后,由这个统一的事件监听器对所收集的事件逐一执行。
这样做的好处:
• 对事件进行归类,可以在事件产生的任务上包含不同的优先级
• 提供合成事件对象,抹平浏览器的兼容性差异
• 减少内存消耗,提升性能,不需要注册那么多的事件了,一种事件类型只在 Root上注册一次
• 对开发者友好

二、 原生事件回顾

JavaScript原生事件的执行需要经过三个阶段,事件捕获-事件目标-事件冒泡。在这里插入图片描述

  1. 事件捕获
    当某个元素触发某个事件(如 onclick ),顶层对象 Document 就会发出一个事件流,随着 DOM 树的节点向目标元素节点流去,直到到达事件真正发生的目标元素。
  2. 事件目标
    当到达目标元素之后,执行目标元素该事件相应的处理函数。如果没有绑定监听函数,那就不执行。
  3. 事件冒泡
    从目标元素开始,往顶层元素传播。途中如果有节点绑定了相应的事件处理函数,这些函数都会被触发一次。
  4. 事件委托/事件代理
    简单理解就是将一个响应事件委托到另一个元素。
    当子节点被点击时,click 事件向上冒泡,父节点捕获到事件后,我们判断是否为所需的节点,然后进行处理。

三、 合成事件与原生事件区别

React合成事件与原生事件很相似,但不完全相同。这里列举几个常见区别:

  1. 事件名称命名方式不同
    原生事件命名为纯小写(如:onclick),而 React 事件命名采用小驼峰式,如 onClick 等:
// 原生事件命名方式
<button onclick="handleClick()">原生事件命名方式</button>

//React合成事件命名方式 
<button onClick={handleClick}>合成事件命名方式 </button>
  1. 事件处理函数写法不同
    原生事件中事件处理函数为字符串,在 React JSX 语法中,传入一个函数作为事件处理函数。
// 原生事件处理函数写法
<button onclick="handleClick()">原生事件处理函数写法</button>

//React合成事件处理函数写法
<button onClick={handleClick}>React合成事件处理函数写法</button>
  1. 阻止默认行为方式不同
    在原生事件中,可以通过返回 false 方式来阻止默认行为,但是在 React 中,需要显式使用e.preventDefault() 方法来阻止。
    这里以阻止 标签默认打开新页面为例,介绍两种事件区别:
// 原生事件阻止默认行为方式
<a href="https://www.baidu.com" 
  onclick="console.log('原生事件阻止默认行为'); return false"
>
  原生事件阻止默认行为
</a>

// React合成事件阻止默认行为方式
const handleClick = e => {
  e.preventDefault();
  console.log('合成事件阻止默认行为');
}
const clickElement = <a href="https://www.baidu.com" onClick={handleClick}>
  合成事件阻止默认行为
</a>

四、 React合成事件与原生事件执行顺序

在 React 中,合成事件会以事件委托方式绑定在Root对象上。这里我们手写一个简单示例来观察 React 事件和原生事件的执行顺序:

class App extends React.Component {
  parentRef;
  childRef;
  constructor(props) {
    super(props);
    this.parentRef = React.createRef();
    this.childRef = React.createRef();
  }
  componentDidMount() {
    document.addEventListener(
      "click",
      (e) => {
        console.log("原生事件:document DOM 事件捕获---");
      },
      true
    );
    this.parentRef.current?.addEventListener(
      "click",
      () => {
        console.log("原生事件:父元素 DOM 事件捕获---");
      },
      true
    );
    this.childRef.current?.addEventListener(
      "click",
      () => {
        console.log("原生事件:子元素 DOM 事件捕获---");
      },
      true
    );
    document.addEventListener(
      "click",
      (e) => {
        console.log("原生事件:document DOM 事件冒泡!");
      },
      false
    );
    this.parentRef.current?.addEventListener(
      "click",
      () => {
        console.log("原生事件:父元素 DOM 事件冒泡!");
      },
      false
    );
    this.childRef.current?.addEventListener(
      "click",
      () => {
        console.log("原生事件:子元素 DOM 事件冒泡!");
      },
      false
    );
  }
  parentClickFun = () => {
    console.log("React 事件:父元素事件冒泡!");
  };
  parentCapture = () => {
    console.log("React 事件:父元素事件捕获-----");
  };
  childClickFun = () => {
    console.log("React 事件:子元素事件冒泡!");
  };
  childCapture = () => {
    console.log("React 事件:子元素事件捕获-----");
  };
  render() {
    return (
      <div
        ref={this.parentRef}
        onClick={this.parentClickFun}
        onClickCapture={this.parentCapture}
      >
        <p
          ref={this.childRef}
          onClick={this.childClickFun}
          onClickCapture={this.childCapture}
        >
          分析事件执行顺序
        </p>
      </div>
    );
  }
}
export default App;

触发事件后,可以看到控制台输出:
在这里插入图片描述
在这里插入图片描述
通过上面流程,我们可以总结得出:
• React 所有事件都委托在root 对象上;
• 当真实 DOM 元素触发事件,会冒泡到 root 对象后,再处理 React 事件;
• 在捕获阶段,先注册的先执行,且React合成事件先于原生事件执行;冒泡阶段,先注册的后执行,且原生事件先于React事件执行

五、 阻止冒泡

原生事件阻止冒泡:可以使用 e.stopPropagation() 或者 e.cancelBubble=true(IE)来阻止事件的冒泡传播。
React阻止冒泡:通常开发者都希望使用 e.stopPropagation() 方法来阻止React的事件冒泡,但事实上,由于所有的合成事件本身都是在 Root上执行的,所以,e.stopPropagation() 只能阻止合成事件间冒泡,即下层的合成事件,不会冒泡到上层的合成事件。
因此,在react中,阻止冒泡的方式有三种:

  1. 阻止合成事件与非合成事件(除了document)之间的冒泡,以上两种方式都不适用,需要用到e.target 判断。
  2. 阻止合成事件与最外层document上的事件间的冒泡,用e.nativeEvent.stopImmediatePropagation();
  3. 阻止合成事件间的冒泡,用e.stopPropagation();

存在代码如下

class StopBubbling extends React.Component {
  componentDidMount() {
    document.body.addEventListener("click", this.handleBodyClick, false);
    document.addEventListener("click", this.handleDocumentClick, false);
  }
 handleDocumentClick = (e) => {
    console.log("handleDocumentClick!");
  };

  handleBodyClick = (e) => {
    console.log("handleBodyClick! ");
  };

  handleClickTestBox = (e) => {
    console.warn("handleClickTestBox!");
  };

  handleClickTestBox2 = (e) => {
    console.warn("handleClickTestBox2!");
  };

  handleClickTestBox3 = (e) => {
    console.warn("handleClickTestBox3!");
  };

  render() {
    return (
      <div
        className="test-box"
        onClick={this.handleClickTestBox}
        style={{ background: "red" }}
      >
        Box
        <div
          onClick={this.handleClickTestBox2}
          style={{ background: "green", margin: "10px" }}
        >
          Box2
          <div
            id="inner"
            onClick={this.handleClickTestBox3}
            style={{ background: "yellow", margin: "10px" }}
          >
            Box3
          </div>
        </div>
      </div>
    );
  }
}

export default StopBubbling;

  • 当没有任何阻止冒泡时,点击Box3输出:

在这里插入图片描述

  • 阻止合成事件与非合成事件(除了document)之间的冒泡
handleBodyClick = (e) => {
  //阻止合成事件与非合成事件(除了document)之间的冒泡
  if (e.target && e.target === document.querySelector("#inner")) {
    return;
  }
  console.log("handleBodyClick!");
};

此时,点击Box3输出:

在这里插入图片描述

  • 阻止合成事件与最外层document上的事件间的冒泡
handleClickTestBox3 = (e) => {
  // 阻止与原生事件的冒泡
  e.nativeEvent.stopImmediatePropagation();
  console.warn("handleClickTestBox3!");
};

此时,点击Box3输出:

在这里插入图片描述

  • 阻止合成事件间的冒泡
handleClickTestBox3 = (e) => {
  // 阻止合成事件的冒泡
  e.stopPropagation();
  console.warn("handleClickTestBox3!");
};

此时,点击Box3输出:

在这里插入图片描述

注意,合成事件和原生事件最好不要混用
因为,React中的执行e.stopPropagation()可以阻止React合成事件冒泡,但是不能阻止原生事件冒泡;反之,在原生事件中执行e.stopPropagation()阻止冒泡行为,会导致所有元素的事件将无法冒泡到Root上,即所有的 React 事件都将无法被注册。

推荐学习:
【React深入】React事件机制
实现React事件机制(1、2)
参考文献:
探索 React 合成事件
react中阻止事件冒泡

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

React事件机制 – 合成事件 的相关文章

  • 流媒体性能 - Canvas 与

    我正在开发一个应用程序 需要通过 webSocket 连接以每秒至少 30 帧的速度持续传输图像 我遇到了一些性能问题 并希望尽我所能进行优化 我想知道使用不断更新的图像之间的性能差异是什么 就像这样 img src someDynamic
  • Javascript 自时间戳以来经过的时间

    我试图通过将其存储在变量中来 缓存 一些信息 如果 2 分钟过去了 我想获取 实时 值 调用 url 如果 2 分钟还没有过去 我想从变量中获取数据 我基本上想要的是 if time passed is less than 2 minute
  • 如何使用 console.log 省略文件/行号

    如今 您可以在 Chrome 的控制台中编写非常好的东西 查看this https developer chrome com devtools docs tips and tricks关联 我也做了一个截图 正如您在屏幕截图中看到的那样 文
  • 如何在Javascript中声明静态变量[重复]

    这个问题在这里已经有答案了 在下面的代码中 我希望有一个计数器来跟踪创建的 Person 对象的数量 这段代码没有这样做 我该如何实现呢 function Person this name Peter this counter this c
  • 如何获得相对于特定父级的偏移量?

    我想获取元素相对于的偏移量特定的父母不是直接的 也不是文档 我在互联网上查找并找到了offset http api jquery com offset and position http api jquery com position jQ
  • 强制执行 show.bind

    我有一个包含数据的表 当从另一个视图触发事件时 我希望视图检查 show bind 语句 问题是该事件没有更改当前视图中的任何数据 foo html tr p canBeRemoved p tr 我正在使用 EventAggregator
  • 无法提取 Typescript 中的对象值

    我一直在尝试将 JavaScript Web 表单转换为 Typescript 但无法弄清楚如何处理以下内容 在 JavaScript 中有效 let fieldValues JSON parse cookieData let keys O
  • 如何垂直打印数组中的字符串元素? [关闭]

    Closed 这个问题需要调试细节 help minimal reproducible example 目前不接受答案 我有一个数组 我想垂直打印每个元素 例如 myArr abc def ghi 输出应该是 a d g b e h c f
  • 标记内的值发生变化时调用函数

    JavaScript 有没有什么方法可以在段落标记的值更改时调用函数 概述 HTML p 00 00 p
  • Backbone.js 应用程序可以逐步增强并可供搜索引擎抓取吗?

    我需要为我的下一个项目实现一个 MVC JavaScript 框架 但它既是一个网站 又是一个 Web 应用程序 是否可以公开数据服务器端 然后解析 URL 以显示 JS 版本 我计划使用 Rails 作为服务器端代码 我写了一个关于这个主
  • 它们是“相同的”吗?代码大战

    这是完整的问题描述 给定两个数组 a 和 b 编写一个函数 comp a b Clojure 中的 compSame a b 来检查这两个数组是否具有 相同 元素以及相同的重数 这里 相同 意味着 b 中的元素是 a 平方中的元素 无论顺序
  • 上传前如何检查图片的宽度和高度

    对于图片上传 我编写了以下 html 代码
  • 使用 jQuery 更改 SVG 元素的“xlink:href”属性

    我正在尝试使用单击事件更改 xlink href 属性 到目前为止它部分有效 这就是我正在做的 HTML a href class ui btn ui corner all ui shadow editIcon style text ali
  • 按钮导致页面重新加载

    我在我的页面上使用 html 和 jquery 在我的 html 中 我有一个按钮 单击该按钮将触发一个功能 当页面加载时 我调用文档准备中的主函数 这是我的代码 div div
  • 正则表达式获取两个方括号之间的数字

    您好 我需要使用正则表达式在 JavaScript 中获取两对方括号内的字符串 这是我的字符串 12 23 asd 到目前为止我尝试的是使用这种模式 d 我需要获得价值12使用正则表达式 您可以使用以下正则表达式 d 这将提取12 from
  • onClick 事件适用于触摸屏设备上的触摸吗?

    我用过onclick我的网站上的活动 但是 当我在谷歌浏览器的开发人员模式移动视图中打开它时 触摸使用鼠标单击的元素没有任何反应 所以我的问题是 我还必须添加吗ontouch事件连同onclick事件或 onClick 事件适用于所有触摸屏
  • Modernizr 未将类应用于 html 标签

    我目前正在构建一个网站 我需要使用 Modernizr 但由于某种原因 它没有将类应用到 html 标签 因为它应该 我的代码如下所示
  • 如何使用react-router将this.state和this.props传递给路由

    我不知道如何从我的父 ReactJS 组件访问方法post路线和组件 使用react router 这是在
  • 保留对 React 状态变量的“引用”

    据我所知 Javascript 中没有指针 我有以下问题 但我想知道是否有一个解决方案让我无法解决 解决方案可能是普通的 Javascript 或者像 Context API 这样的 React js 钩子 useContext 或者更多
  • 使用 Three.js 中的设备方向控件进行对象旋转

    我正在迈出使用 JavaScript 进行编码并使用 Three js 的第一步 我正在尝试 Threejs org 的这个例子 http trijs org examples misc controls deviceorientation

随机推荐