React合成事件原理简单实现

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 所示的顺序触发事件。

React合成事件原理简单实现_第1张图片

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

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

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

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

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

3.1.2、事件委托

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

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

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

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

    • 事件名称

    • 事件处理函数语法不同

      // 原生事件语法
      
      // React 合成事件语法
      const 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>Documenttitle>
    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>
    
  • 效果

React合成事件原理简单实现_第2张图片

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>Documenttitle>
    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>
    
  • 效果

React合成事件原理简单实现_第3张图片

4、结语

  • 实际上React合成事件涉及到很多内容,实现非常复杂,我们这里只是简单实现了下。
  • 参考链接
    • https://juejin.cn/post/6955636911214067720#heading-26
    • https://www.bilibili.com/video/BV1tK4y1R7Kt/?spm_id_from=333.999.0.0
  • 参考书籍
    • 《JavaScript高级程序设计-第四版》

你可能感兴趣的:(react学习,react.js,javascript,前端,前端框架)