React合成事件原理简单实现

2023-10-29

1、React合成事件是什么?

React 合成事件(SyntheticEvent)是 React 模拟原生 DOM 事件所有能力的一个事件对象,即浏览器原生事件的跨浏览器包装器。它根据 W3C 规范 来定义合成事件,兼容所有浏览器,拥有与浏览器原生事件相同的接口。

2、为什么会有合成事件?

  1. 将事件绑定在document - v16/容器元素 - v17统一管理,防止很多事件直接绑定在原生的dom元素上。造成一些不可控的情况
  2. React 想实现一个全浏览器的框架, 为了实现这种目标就需要提供全浏览器一致性的事件系统,以此抹平不同浏览器的差异。

3、深入合成事件

3.1、事件工作流回顾

3.1.1、事件流

DOM2 Events 规范规定事件流分为 3 个阶段:事件捕获、到达目标和事件冒泡。事件捕获最先发生,为提前拦截事件提供了可能。然后,实际的目标元素接收到事件。最后一个阶段是冒泡,最迟要在这个阶段响应事件。以前面那个简单的 HTML 为例,点击

元素会以如图 17-3 所示的顺序触发事件。

在这里插入图片描述

在 DOM 事件流中,实际的目标(

元素)在捕获阶段不会接收到事件。这是因为捕获阶段从

document 到再到就结束了。下一阶段,即会在

元素上触发事件的“到达目标”

阶段,通常在事件处理时被认为是冒泡阶段的一部分。然后,冒泡阶段开始,事件反向传播至文档。

3.1.2、事件委托

因为事件处理程序在现代 Web 应用中可以实现交互,所以很多开发者会错误地在页面中大量使用它们。在创建 GUI 的语言如 C#中,通常会给 GUI 上的每个按钮设置一个 onclick 事件处理程序。这样做不会有什么性能损耗。在 JavaScript 中,页面中事件处理程序的数量与页面整体性能直接相关。原因有很多。首先,每个函数都是对象,都占用内存空间,对象越多,性能越差。其次,为指定事件处理程序所需访问 DOM 的次数会先期造成整个页面交互的延迟。只要在使用事件处理程序时多注意一些方法,就可以改善页面性能。

“过多事件处理程序”的解决方案是使用事件委托。事件委托利用事件冒泡,可以只使用一个事件

处理程序来管理一种类型的事件。

3.2、事件差异

3.2.1、合成事件与原生事件的差异
  • 语法不同

    • 事件名称

    • 事件处理函数语法不同

      // 原生事件语法
      <button οnclick="handleClick()">点我</button>
      // React 合成事件语法
      const button = <button onClick={handleClick}>点我</button>
      
  • 阻止默认行为方式不同

    • 原声可以通过return false / e.preventDefault() 阻止默认行为
    • 合成事件需要使用 e.preventDefault()return false 无效 (addEventListener)
3.2.2、React16和17合成事件的差异
  • React16时事件委托的对象是 document,React17时事件委托的对象是容器组件
    • 这样做是有利于微前端的,微前端一个前端系统中可能有多个应用,如果继续采取全部绑定在document上,那么可能多应用下会出现问题。
  • React16时原生事件与React事件执行时,冒泡阶段与捕获阶段没有区分开(原声捕获-> 原声冒泡 -> React捕获 -> React冒泡);React17时优化了合成事件的执行,当与原生事件一起调用时,捕获阶段总是先于冒泡阶段(React捕获 ->原声捕获 -> 原声冒泡 -> React冒泡)
  • react17废弃了事件池

3.3、简单实现合成事件

3.3.1、React17以前
  • 简单模拟

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
    </head>
    <body>
      <div class="root">
        <div class="parent">
          <div class="child">
            点击
          </div>
        </div>
      </div>
    </body>
    </html>
    <script>
      
    document.getElementsByClassName('parent')[0].addEventListener('click',()=>{
      console.log("原生事件捕获:parent")
    },true)
    
    document.getElementsByClassName('child')[0].addEventListener('click',()=>{
      console.log("原生事件捕获:child")
    },true)
    
    document.getElementsByClassName('parent')[0].addEventListener('click',()=>{
      console.log("原生事件冒泡:parent")
    })
    
    document.getElementsByClassName('child')[0].addEventListener('click',()=>{
      console.log("原生事件冒泡:child")
    })
    
    document.getElementsByClassName('parent')[0].onClick =  function(){
      console.log("react合成事件冒泡:parent")
    }
    
    document.getElementsByClassName('child')[0].onClick = function(){
      console.log("react合成事件冒泡:child");
    }
    
    
    document.getElementsByClassName('parent')[0].onClickCapture =  function(){
      console.log("react合成事件捕获:parent")
    }
    
    document.getElementsByClassName('child')[0].onClickCapture = function(){
      console.log("react合成事件捕获:child");
    }
    
    
    const dispatchEvent = (e)=>{
      let paths = []
    	let current = e.target;
    	while(current){
    		paths.push(current);
    		current = current.parentNode;
    	}
      
    	// 模拟捕获与冒泡
      
    	for(let i= paths.length - 1;i>=0;i--){
        let handler = paths[i].onClickCapture;
    		handler && handler();
    	}
      
    	for (var i = 0; i < paths.length; i++) {
        let handler = paths[i].onClick;
    		handler && handler();
    	}
    }
    // 绑定合成事件
    document.addEventListener('click',dispatchEvent);
    </script>
    
  • 效果

在这里插入图片描述

3.3.2、React17以后
  • 简单模拟

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
    </head>
    <body>
      <div id="root">
        <div class="parent">
          <div class="child">
            点击
          </div>
        </div>
      </div>
    </body>
    </html>
    <script>
      
    document.getElementsByClassName('parent')[0].addEventListener('click',()=>{
      console.log("原生事件捕获:parent")
    },true)
    
    document.getElementsByClassName('child')[0].addEventListener('click',()=>{
      console.log("原生事件捕获:child")
    },true)
    
    document.getElementsByClassName('parent')[0].addEventListener('click',()=>{
      console.log("原生事件冒泡:parent")
    })
    
    document.getElementsByClassName('child')[0].addEventListener('click',()=>{
      console.log("原生事件冒泡:child")
    })
    
    document.getElementsByClassName('parent')[0].onClick =  function(){
      console.log("react合成事件冒泡:parent")
    }
    
    document.getElementsByClassName('child')[0].onClick = function(){
      console.log("react合成事件冒泡:child");
    }
    
    
    document.getElementsByClassName('parent')[0].onClickCapture =  function(){
      console.log("react合成事件捕获:parent")
    }
    
    document.getElementsByClassName('child')[0].onClickCapture = function(){
      console.log("react合成事件捕获:child");
    }
    
    
    const dispatchEvent = (e,useCapture)=>{
      let paths = []
      let current = e.target;
      while(current){
        paths.push(current);
        current = current.parentNode;
      }
    
      // 模拟捕获与冒泡
    
      if(useCapture){
        for(let i= paths.length - 1;i>=0;i--){
          let handler = paths[i].onClickCapture;
          handler && handler();
        }
      }else{
        for (var i = 0; i < paths.length; i++) {
          let handler = paths[i].onClick;
          handler && handler();
        }
      }
    }
    // 绑定合成事件
    document.getElementById('root').addEventListener('click',(e)=>dispatchEvent(e,true),true);
    document.getElementById('root').addEventListener('click',(e)=>dispatchEvent(e,false));
    </script>
    
  • 效果

在这里插入图片描述

4、结语

  • 实际上React合成事件涉及到很多内容,实现非常复杂,我们这里只是简单实现了下。
  • 参考链接
    • https://juejin.cn/post/6955636911214067720#heading-26
    • https://www.bilibili.com/video/BV1tK4y1R7Kt/?spm_id_from=333.999.0.0
  • 参考书籍
    • 《JavaScript高级程序设计-第四版》
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

React合成事件原理简单实现 的相关文章

  • Angular JS - 提交到 $http 时日期发生变化 - 时区问题

    我遇到一个奇怪的问题Date当它通过 http put 传递到 API 时发生变化 我怀疑时区问题 Datepicker 触发 ng change 事件 console log Tue Jun 10 2014 00 00 00 GMT 01
  • Iphone 上的 Javascript 日期解析

    我正在开发一个针对移动设备的离线 Javascript 网站 iPhone 就是这样的一种移动设备 我正在尝试从 REST API JSON 对象的成员 解析日期 我在用着 Date parse 2010 03 15 10 30 00 这适
  • javascript - 动态变量

    您好 我正在尝试创建动态变量 但它说 变量 v0 到 v5 未定义 td each function i eval var v i this html 任何帮助将不胜感激 这听起来是个坏主意 你有什么理由不能这样做吗 var tdHtml
  • 在气球内显示带有照片的多个地标的最佳做法是什么?

    我有一个项目如下 从手机上拍摄几张照片 将照片保存在网络系统中 然后将照片显示在其中的谷歌地球上 我读过很多文章 但它们都使用 fetchKml 我读过的一篇好文章是使用 php 但使用 fetchKml 我不知道是否可以使用 parseK
  • Firefox(仅限)动态表单操作不起作用

    控制台为操作属性返回空白 我已经移动了 file upload attr action io cfm action updateitemfile item id agenda modal attr data defaultitemid 周围
  • 定时器内嵌套异步等待 - 不返回所需的值

    我必须使用 Mocha 和 chai 测试来测试端点的响应 下面是相同的代码 async function getData userId let response let interval setInterval async gt resp
  • History.pushState和页面刷新

    我开始研究 HTML5 新历史 API 不过 我有一个问题 如何处理页面刷新 例如 用户单击一个链接 该链接由 js 函数处理 该函数 异步加载页面内容 使用history pushState 更改URL 用户刷新页面 但是服务器上当然不存
  • React Query useInfiniteQuery 使单个项目无效

    使用 useInfiniteQuery 时如何使单个项目无效 这是一个演示我想要实现的目标的示例 假设我有一个成员列表 每个成员都有一个关注按钮 当我按下 关注 按钮时 会单独调用服务器来标记给定用户正在关注另一个用户 此后 我必须使整个无
  • 检测 iPad Safari 用户的最佳方法

    添加用于检测 iPad Safari 用户的代码的最佳方法是什么 我的意思是我们应该使用 1 CSS 通过链接媒体 2 JS 通过navigator对象 我听说使用用户代理字符串并不是检测 iPad 的最佳方法 因为存在不一致的情况 请建议
  • 使用点符号将数字传递到函数中

    如果我有一个对象和函数 var obj 1234 example sample 5678 example sample function example num str if obj num hasOwnProperty str manip
  • 如何像在浏览器中一样检索准确的 HTML

    我正在使用 Python 脚本来呈现网页并检索其 HTML 它适用于大多数页面 但对于其中一些页面 检索到的 HTML 不完整 我不太明白为什么 这是我用来废弃此页面的脚本 由于某种原因 每个产品的链接不在 HTML 中 Link http
  • 将 DIV 转换为单击并拖动视口

    有人知道一种不显眼的 基于原型或无框架的方法将具有大内容 例如地图 的 DIV 转换为具有固定尺寸的可点击和可拖动的 地图 容器 非常像 Google 地图 我想在大型输入表单中显示 HTML 块 这些块可能会超出可用空间 每个块可以有大约
  • 如何在javascript中删除一组表情符号中的最后一个表情符号?

    假设我的字符串中有 3 个表情符号 字符串中没有任何空格或除表情符号之外的任何其他字符 如何删除javascript中最后一个表情符号 下面的答案不使用任何特殊的包并安全地删除最后一个表情符号 function safeEmojiBacks
  • 无法安装js-bson

    我正在使用Windows 7 64位 尝试安装bson作为mongodb的依赖项 我收到此错误 npm WARN package json email protected cdn cgi l email protection No READ
  • 如何在没有 DOM 的情况下将 javascript 作为 node.js 脚本运行?

    https github com jasondavies d3 cloud https github com jasondavies d3 cloud是一个使用 D3 库的 javascript 文字云 这是一个交互式演示 http www
  • 使用 Google 地图 API 进行反向地理编码

    我正在研究 JavaScript Google Map API 版本 3 更准确地说 正在研究反向地理定位 在 的帮助下官方文档 http code google com intl fr apis maps documentation ge
  • useEffect 中的 useState 不更新状态

    我是 React Hooks 新手 正在使用 React 16 13 1 我要实施Auth能够处理登录的组件 但似乎没有更新状态currentUser正确地 尽管setCurrentUser使用响应对象调用 这段代码有什么问题 import
  • 为什么 [].push([]) 返回 1? [复制]

    这个问题在这里已经有答案了 为什么这会返回 1 push outputs 1 push 返回数组的新长度 one push two returns 2 array length is 2 one two push something ret
  • 在javascript中我们如何识别一个对象是Hash还是Array?

    我的 JSON 调用的输出可以是数组或哈希 我如何区分这两者 现代浏览器支持Array isArray obj method See MDN https developer mozilla org en US docs Web JavaSc
  • 类型错误:无法读取未定义的属性“长度” - 使用安全帽部署时

    我在尝试在安全帽开发链上部署模拟合约时收到以下错误 我正在关注 使用 JavaScript 学习区块链 Solidity 和全栈 Web3 开发 Patrick Collins 在 FreeCodeCamp YT 频道上的 32 小时课程

随机推荐

  • HTML文件路径

    目录 HTML 文件路径 绝对文件路径 实例 相对路径 实例 实例 实例 好习惯 路径 描述 img src picture jpg picture jpg 位于与当前网页相同的文件夹 img src images picture jpg
  • Panoptic SegFormer:全景分割第一名!南大&港大&英伟达提出新算法,霸榜全景分割

    轻量级全景分割 模型50多m 有预测代码 没有训练 GitHub midasklr PPLiteSeg pytorch pytorch of the SOTA real time segmentation network ppliteseg
  • ubuntu/WSL 2.0 解决无法通过apt安装jdk的问题

    WSL 2 0 下无法通过apt安装jdk问题解决 问题描述 解决方法 总结 问题描述 在win11的wsl下通过apt安装jdk包时遇到了下列问题 执行这个安装命令 sudo apt get install openjdk 8 jdk 结
  • 大厂都在做的jmeter接口自动化测试登峰造极的jmeter实现接口自动化测试

    一 JMETER的环境搭建 参考 https www cnblogs com qmfsun p 4902534 html 二 JMETER的汉化 临时汉化方法 打开jmeter options gt choose language gt 选
  • 电机学习笔记 输出比较

    一 输入比较简介 输出比较就是通过定时器的外部引脚对外输出控制信号 有冻结 将通道 X x 1 2 3 4 设置为匹配时输出有效电平 将通道 X 设置为匹配时输出无效电平 翻转 强制变为无效电平 强制变为有效电平 PWM1 和 PWM2 这
  • 机房布线的最高境界……

    点击上方 Java基基 选择 设为星标 做积极的人 而不是积极废人 源码精品专栏 原创 Java 2020 超神之路 很肝 中文详细注释的开源项目 RPC 框架 Dubbo 源码解析 网络应用框架 Netty 源码解析 消息中间件 Rock
  • 【OpenWRT之旅】LuCI探究

    1 多语言 1 检查 opkg list grep luci i18n 2 安装语言包 opkg install luci i18n hungarian 2 uhttpd 这个是LuCI所在的Web Server docroot在 www下
  • LeetCode5912. 每一个查询的最大美丽值(排序+优先队列)

    LeetCode5912 每一个查询的最大美丽值 排序 优先队列 题目传送门 题目 给你一个二维整数数组 items 其中 items i pricei beautyi 分别表示每一个物品的 价格 和 美丽值 同时给你一个下标从 0 开始的
  • C语言中的字符串数组

    代码 include
  • Windows电脑安装Linux系统的方法-Ubuntu版

    本文内容均来自B站视频如何安装Linux与Windows双系统 只是把视频内容整理成文章形式便于速看 如有侵权 联系立删 从已经有系统U盘开始说起 前面缺失的内容后面会整理加上 U盘插到要安装的电脑上 此电脑 右键 管理 磁盘管理 找到一个
  • LLVM系列第二十二章:写一个简单的编译时函数调用统计器(Pass)

    系列文章目录 LLVM系列第一章 编译LLVM源码 LLVM系列第二章 模块Module LLVM系列第三章 函数Function LLVM系列第四章 逻辑代码块Block LLVM系列第五章 全局变量Global Variable LLV
  • 硅基生命之漫谈-2:宇宙之基本法则:聚合与分解?

    聚合与分解是宇宙的基本法则 聚合是万物存在和发展的形式 是重组基本粒子的过程 分解是万物存在和死亡的形式 是回归基本粒子的过程 分解的目的是为了重组聚合 重组聚合的结果会重新分解 太极图 八卦图 人的生命体 万物的生命体 无机体 人类社会的
  • 2023.05.30-ubuntu22.04多卡服务器卸载cuda重新安装(踩坑不断版本)

    目录 说明 1 驱动问题 1 1 卸载驱动 1 2 安装驱动 2 cuda卸载 2 1 卸载用run方式安装的CUDA和驱动 2 2 卸载用deb方式安装的CUDA 3 cudn cudnn安装 3 1 cuda11 7安装 3 2 cud
  • 【PTA】L2-035 完全二叉树的层序遍历

    L2 035 完全二叉树的层序遍历 25分 一个二叉树 如果每一个层的结点数都达到最大值 则这个二叉树就是完美二叉树 对于深度为 D 的 有 N 个结点的二叉树 若其结点对应于相同深度完美二叉树的层序遍历的前 N 个结点 这样的树就是完全二
  • Android WebView 在开发过程中有哪些坑?

    https www zhihu com question 31316646 作者 李明亮 链接 https www zhihu com question 31316646 answer 52714778 来源 知乎 著作权归作者所有 转载请
  • 华为OD机试真题- 字符串重新排序【2023Q1】【JAVA、Python、C++】

    题目描述 给定一个字符串s s包含以空格分隔的若干个单词 请对s进行如下处理后输出 1 单词内部调整 对每个单词字母重新按字典序排序 2 单词间顺序调整 1 统计每个单词出现的次数 并按次数降序排列 2 次数相同时 按单词长度升序排列 3
  • 盒子模型的理解

    盒子模型 什么是盒子 html中的标签 元素 统统都是一个矩形的平面框 在立体上 它由多个平面构成 这称为盒子模型 从底层到顶层的立体结构 margin gt background color gt background image gt
  • CUDA各版本下载

    CUDA及CUDNN各版本下载地址 1 CUDA各版本下载地址 https developer nvidia com cuda toolkit archive CUDA 各版本下载 这个网址有点难找 2 CuDNN下载地址 需要登录 htt
  • 【英语】大学英语CET考试,口语部分2(课程笔记)

    文章目录 1 口语考试介绍与备考攻略 1 1 口语考试介绍 1 2 考试备考攻略 2 其他补充 考前 2 1 一些补充 2 2 口语考试培训 重要 上课老师 金格妃 上课时间 4h 1 口语考试介绍与备考攻略 方法是通用的 CET4 6 雅
  • React合成事件原理简单实现

    1 React合成事件是什么 React 合成事件 SyntheticEvent 是 React 模拟原生 DOM 事件所有能力的一个事件对象 即浏览器原生事件的跨浏览器包装器 它根据 W3C 规范 来定义合成事件 兼容所有浏览器 拥有与浏