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合成事件原理简单实现 的相关文章

随机推荐

  • 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 规范 来定义合成事件 兼容所有浏览器 拥有与浏