React 事件概述

  1. React 根据 W3C 规范来定义自己的事件系统,其事件被称之为合成事件 (SyntheticEvent),而其自定义事件系统的动机主要包含以下几个方面:
    1. 抹平不同浏览器之间的兼容性差异,最主要的动机;
    2. 既可以处理兼容性问题,也可以用来自定义事件 (例如 React 的 onChange 事件)
    3. 提供一个抽象跨平台事件机制,类似 VirtualDOM 抽象了跨平台的渲染方式,合成事件 (SyntheticEvent) 提供一个抽象的跨平台事件机制;
    4. 可以做更多优化,例如利用事件委托机制,几乎所有事件的触发都代理到了 document,而不是 DOM 节点本身,简化了 DOM 事件处理逻辑,减少了内存开销;(React 自身模拟了一套事件冒泡的机制)
      • 因为合成事件的触发是基于浏览器的事件机制来实现的,通过冒泡机制冒泡到最顶层元素,然后再由 dispatchEvent 统一去处理;
      • 节点上的原生事件的执行是在目标阶段,然而合成事件的执行是在冒泡阶段,所以原生事件会先合成事件执行,然后再往父节点冒泡;
      • 原生事件阻止冒泡肯定会阻止合成事件的触发;
      • 合成事件的阻止冒泡不会影响原生事件;
    5. 可以干预事件的分发V16 引入 Fiber 架构,React 可以通过干预事件的分发以优化用户的交互体验;
  2. 注:「几乎」所有事件都代理到了 document,说明有例外,比如 audiovideo 标签的一些媒体事件 (如 onplay、onpause 等),是 document 所不具有,这些事件只能够在这些标签上进行事件进行代理,但依旧用统一的入口分发函数 (dispatchEvent) 进行绑定;

合成事件的理解

封装原生事件

  1. SyntheticEventreact 合成事件的基类,定义了合成事件的基础公共属性和方法;事件的回调方法中的参数 e,其实不是原生事件对象,而是 react 包装过的对象,同时原生事件对象被放在了属性 e.nativeEvent 内;

  2. react 会根据当前的事件类型来使用不同的合成事件对象,比如鼠标单机事件 SyntheticMouseEvent,焦点事件 SyntheticFocusEvent 等,但是都是继承自 SyntheticEvent

升级和改造原生事件

  1. 对于有些 dom 元素事件,进行事件绑定之后,react 并不是只处理声明的事件类型,还会额外的增加一些其他的事件,帮助我们提升交互的体验;

  2. 当给 input 声明个 onChange 事件,可以看到 react 不只是注册了一个 onchange 事件,还注册了很多其他事件,而这个时候向文本框输入内容的时候,是可以实时的得到内容的;然而原生只注册一个 onchange 的话,需要在失去焦点的时候才能触发这个事件,所以这个原生事件的缺陷 react 也帮我们弥补了;

处理浏览器的兼容性

  1. react 在给 document 注册事件的时候也是对兼容性做了处理;

  2. document 注册事件,内部其实也是做了对 ie 浏览器的兼容做了处理;

事件注册机制

事件注册

  1. React 组件挂载阶段,根据组件内的声明的事件类型 (onclick、onchange 等),使用 addEventListener('事件类型', dispatchEvent) 在 document 上注册事件 (从这里也可以看出 React 的事件是在 DOM 事件流的冒泡阶段被触发执行),并指定统一的回调函数 dispatchEvent

  2. 换句话说,document 上不管注册的是什么事件,都具有统一的回调函数 dispatchEvent;也正是因为这一事件委托机制,所以对于同一种事件类型,不论在 document 上注册了几次,最终也只会保留一个有效实例,这能减少内存开销;

事件存储

  1. React 为了在触发事件时可以查找到对应的回调去执行,会把组件内的所有事件统一地存放到一个对象中 (listenerBank)

  2. 存储方式如上图,首先会根据事件类型分类存储,例如 click 事件相关的统一存储在一个对象中,回调函数的存储采用键值对 (key/value) 的方式存储在对象中,key 是组件的唯一标识 idvalue 对应的就是事件的回调函数;

事件执行机制

  1. React 的事件触发只会发生在 DOM 事件流的冒泡阶段,因为在 document 上注册时就默认是在冒泡阶段被触发执行;

  2. 其大致流程如下:

    1. 进入统一的事件分发函数 (dispatchEvent)
    2. 结合原生事件找到当前节点对应的 ReactDOMComponent 对象;
    3. 开始 事件的合成:
      1. 根据当前事件类型生成指定的合成对象;
      2. 封装原生事件和冒泡机制;
      3. 查找当前元素以及他所有父级;
      4. listenerBank 查找事件回调并合成到 event (合成事件结束)
    4. 批量处理合成事件内的回调事件 (事件触发完成 end)

注意事项

  1. 如果给真实的 DOM 注册事件,阻止了事件冒泡,则会导致 react 的相应事件无法触发;

  2. 如果给真实的 DOM 注册事件,原生事件 会先于 React 事件 运行;

  3. 通过 React 的事件中阻止事件冒泡,无法阻止真实的 DOM 事件冒泡;

  4. React 事件 可以通过 e.nativeEvent.stopImmediatePropagation(),阻止 document 上剩余事件的执行;

  5. 在事件处理程序中,不要异步的使用事件对象,如果一定要使用,需要先调用 e.persist()

打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

中午好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. React 事件概述
  2. 2. 合成事件的理解
    1. 2.1. 封装原生事件
    2. 2.2. 升级和改造原生事件
    3. 2.3. 处理浏览器的兼容性
  3. 3. 事件注册机制
    1. 3.1. 事件注册
    2. 3.2. 事件存储
  4. 4. 事件执行机制
  5. 5. 注意事项