• 细说react源码中的合成事件


    💕 温馨提示: 下边是对React合成事件的源码阅读,全文有点长,但是!如果你真的想知道这不为人知的背后内幕,那一定要耐心看下去!

    最近在做一个功能,然后不小心踩到了 React 合成事件 的坑,好奇心的驱使,去看了 React 官网合成事件 的解释,这不看不知道,一看吓一跳…

    SyntheticEvent是个什么鬼?咋冒出来了个事件池

    我就一个简单的需求功能,为什么能扯出这些鬼玩意??

    我们先简单的来看一看我的需求功能是个啥???

    导火线

    需要做一个弹窗打开/关闭 的功能,当点击 button 的时候打开,此时打开的情况下,点击弹窗 区域 外,就需要关闭。

    这简单嘛,直接在 button 上注册一个点击事件,同时在 document.body 注册一个点击事件,然后在 弹窗container阻止冒泡,很难嘛?

    class FuckEvent extends React.PureComponent {
       
      state = {
       
        showBox: false
      }
      componentDidMount() {
       
        document.body.addEventListener('click', this.handleClickBody, false)
      }
      componentWillUnmount() {
       
        document.body.removeEventListener('click', this.handleClickBody, false)
      }
      handleClickBody = () => {
       
        this.setState({
       
          showBox: false
        })
      }
      handleClickButton = () => {
       
        this.setState({
       
          showBox: true
        })
      }
    
      render() {
       
        return (
          <div>
            <button onClick={
       this.handleClickButton}>点击我显示弹窗</button>
    
            {
       this.state.showBox && (          <div onClick={
       e => e.stopPropagation()}>我是弹窗</div>
            )}      </div>
        )
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    很简单嘛,很开心的点击了弹窗区域…

    于是…我没了…点击弹窗区域,弹窗也被关闭了。。。what the f**k ??? 难道冒泡没有用 ?

    带着这个问题,我走上了不归之路

    事件委托

    我们都知道,什么是事件委托,(不知道的出门左拐 👈) 在前端刀耕火种时期,事件委托可是爸爸

    事件委托解决了庞大的数据列表时,无需为每个列表项绑定事件监听。同时可以动态挂载元素无需作额外的事件监听处理。

    你看,事件委托那么牛 13,你觉得 React 会不用?呵,React 不仅用了,还用的非常溜 ~

    怎么说呢,react 它接管了浏览器事件的优化策略,然后自身实现了一套自己的事件机制,而且特别贴心,就跟你男朋友一样,它把浏览器的不同差异,都帮你消除了 ~

    React 实现了一个合成事件层,就是这个事件层,把 IE 和 W3C 标准之间的兼容问题给消除了。

    📌 那么问题来了,什么是合成事件与原生事件???

    • 原生事件: 在 componentDidMount生命周期里边进行addEventListener绑定的事件

    • 合成事件: 通过 JSX 方式绑定的事件,比如 onClick={() => this.handle()}

    还记得上边的那个例子吗?我们在弹窗的 DOM 元素上绑定了一个事件,进行阻止冒泡

    相关参考视频讲解:进入学习

    {
       
      this.state.showBox && <div onClick={
       e => e.stopPropagation()}>我是弹窗</div>
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    然后在componentDidMount生命周期里边对 body 进行了 click 的绑定

    componentDidMount() {
       
      document.body.addEventListener('click', this.handleClickBody, false)
    }
    
    componentWillUnmount() {
       
      document.body.removeEventListener('click', this.handleClickBody, false)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    我们去分析一下,因为合成事件的触发是基于浏览器的事件机制来实现的,通过冒泡机制冒泡到最顶层元素,然后再由 dispatchEvent 统一去处理

    回顾一下浏览器事件机制

    Document 上边是 Window,这里截的是《JavaScript 高级程序设计》书籍里的图片

    浏览器事件的执行需要经过三个阶段,捕获阶段-目标元素阶段-冒泡阶段。

    🙋 Question: 此时对于合成事件进行阻止,原生事件会执行吗?答案是: 会!

    📢 Answer: 因为原生事件先于合成事件执行 (个人理解: 注册的原生事件已经执行,而合成事件处于目标阶段,它阻止的冒泡只是阻止合成的事件冒泡,但是原生事件在捕获阶段就已经执行了)

    合成事件特点

    React 自己实现了这么一套事件机制,它在 DOM 事件体系基础上做了改进,减少了内存的消耗,并且最大程度上解决了 IE 等浏览器的不兼容问题

    那它有什么特点?

    • React 上注册的事件最终会绑定在document这个 DOM 上,而不是 React 组件对应的 DOM(减少内存开销就是因为所有的事件都绑定在 document 上,其他节点没有绑定事件)

    • React 自身实现了一套事件冒泡机制,所以这也就是为什么我们 event.stopPropagation() 无效的原因。

    • React 通过队列的形式,从触发的组件向父组件回溯,然后调用他们 JSX 中定义的 callback

    • React 有一套自己的合成事件 SyntheticEvent,不是原生的,这个可以自己去看官网

    • React 通过对象池的形式管理合成事件对象的创建和销毁,减少了垃圾的生成和新对象内存的分配,提高了性能

    React 事件系统

    看到这里,应该对 React 合成事件有一个简单的了解了吧,我们接着去看一看源码 ~

    👉 源码 ReactBrowserEventEmitter

    我们在 ReactBrowserEventEmitter.js 文件中可以看到,React 合成系统框架图

    /**
     * React和事件系统概述:
     *
     * +------------+    .
     * |    DOM     |    .
     * +------------+    .
     *       |           .
     *       v           .
     * +------------+    .
     * | ReactEvent |    .
     * |  Listener  |    .
     * +------------+    .                         +-----------+
     *       |           .               +--------+|SimpleEvent|
     *       |           .               |         |Plugin     |
     * +-----|------+    .               v         +-----------+
     * |     |      |    .    +--------------+                    +------------+
     * |     +-----------.--->|EventPluginHub|                    |    Event   |
     * |            |    .    |              |     +-----------+  | Propagators|
     * | ReactEvent |    .    |              |     |TapEvent   |  |------------|
     * |  Emitter   |    .    |              |<---+|Plugin     |  |other plugin|
     * |            |    .    |              |     +-----------+  |  utilities |
     * |     +-----------.--->|              |                    +------------+
     * |     |      |    .    +--------------+
     * +-----|------+    .                ^        +-----------+
     *       |           .                |        |Enter/Leave|
     *       +           .                +-------+|Plugin     |
     * +-------------+   .                         +-----------+
     * | application |   .
     * |-------------|   .
     * |             |   .
     * |             |   .
     * +-------------+   .
     *                   .
     */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    源码里边的一大串英文解释,我帮你们 google 翻译了,简单来讲就是:

    • Top-level delegation 用于捕获最原始的浏览器事件,它主要由 ReactEventListener 负责,ReactEventListener 被注入后可以支持插件化的事件源,这一过程发生在主线程。

    • React 对事件进行规范化和重复数据删除,以解决浏览器的怪癖。这可以在工作线程中完成。

    • 将这些本地事件(具有关联的顶级类型用来捕获它)转发到EventPluginHub,后者将询问插件是否要提取任何合成事件。

    • 然后,EventPluginHub 将通过为每个事件添加“dispatches”(关心该事件的侦听器和 ID 的序列)来对其进行注释来进行处理。

    • 再接着,EventPluginHub 会调度分派事件.

    ❗ 建议直接去看英文注释,翻译可能不是很标准。

    看会上边的框架图,我们得先知道一下这些都是个啥玩意,直接看名称,也能

  • 相关阅读:
    如何一步步优化负载均衡策略
    Java文件上传及下载
    [附源码]java毕业设计游戏战队考核系统
    MFC子类控件化
    Vue生命周期函数相关——笔试/面试题
    sql 注入(1), union 联合注入
    synchronized的工作原理
    python-pdf的合并与拆分
    翻译|是否应该在 Kubernetes 上运行数据库?
    ZooKeeper
  • 原文地址:https://blog.csdn.net/weixin_59558923/article/details/127859150