React合成事件内部分享记录

这篇博客深入探讨了React的合成事件机制,解释了为何在特定情况下点击事件会失效。内容包括React自定义事件系统的理由,如兼容性和跨平台需求,以及事件处理的基本流程,涉及到事件委托、事件处理器和插件管理。最后,博主通过一个实例分析了事件冒泡和捕获的顺序,揭示了原生事件与React合成事件的关系。
摘要由CSDN通过智能技术生成

从一个栗子开始

实现一个组件,这个组件点击按钮会显示一个二维码,点击二维码之外的区域可以隐藏二维码,但是点击二维码本身却不会关闭

class Demo extends Component {
  constructor(props) {
    super(props);
    this.state = {
      active: false,
    };
  }
  
  componentDidMount() {
    document.body.addEventListener('click', e => {
      this.setState({
        active: false,
      });
    });
  }

  componentWillUnmount() {
    document.body.removeEventListener('click');
  }
  
  handleClick = () => {
    this.setState({
      active: !this.state.active,
    });
  }
  
  handleClickQr = (e) => {
    e.stopPropagation();
  }

  render() {
    return (
      <div className="qr-wrapper">
        <button className="qr" onClick={this.handleClick}>召唤二维码</button>
        <div
          className="code"
          style={{ display: this.state.active ? 'block' : 'none' }}
          onClick={this.handleClickQr}
        >
          <div style={{width: '100px', height: '100px', background: '#ff0050'}}>假装是二维码</div>
        </div>
      </div>
    );
  }
}

看上去确实可以实现要求,但事实上运行上述代码后可以发现,点击二维码本身也会导致二维码的隐藏。好神奇啊,这是为什么呢?

SyntheticEvent合成事件

React自身实现了一套自己的事件机制,包括事件注册、事件的合成、事件冒泡、事件派发等,虽然和原生的是两码事,但也是基于浏览器的事件机制下完成的。

为什么自定义一套事件系统

  • 抹平浏览器之间的兼容性差异: React根据W3C规范来定义这些合成事件SyntheticEvent,不会存在IE标准的兼容性问题,在各个浏览器之间达到统一,减少开发者的额外兼容工作。
  • 跨平台 : 跟Virtual DOM一样,VirtualDOM抽象了跨平台的渲染方式,对应的SyntheticEvent目的也是想提供一个抽象的跨平台事件机制。
  • 原生事件升级、改造:事件合成除了处理兼容性问题,还对原生事件做了升级改造,比较典型的是React的onChange事件,它为表单元素定义了统一的值变动事件,例如blurchangefocusinput等。

基本流程

 /**
 * Summary of `ReactBrowserEventEmitter` event handling:
 *
 *  - Top-level delegation is used to trap most native browser events. This
 *    may only occur in the main thread and is the responsibility of
 *    ReactDOMEventListener, which is injected and can therefore support
 *    pluggable event sources. This is the only work that occurs in the main
 *    thread.
 *
 *  - We normalize and de-duplicate events to account for browser quirks. This
 *    may be done in the worker thread.
 *
 *  - Forward these native events (with the associated top-level type used to
 *    trap it) to `EventPluginHub`, which in turn will ask plugins if they want
 *    to extract any synthetic events.
 *
 *  - The `EventPluginHub` will then process each event by annotating them with
 *    "dispatches", a sequence of listeners and IDs that care about that event.
 *
 *  - The `EventPluginHub` then dispatches the events.
 *
 * Overview of React and the event system:
 *
 * +------------+    .
 * |    DOM     |    .
 * +------------+    .
 *       |           .
 *       v           .
 * +------------+    .
 * | ReactEvent |    .
 * |  Listener  |    .
 * +------------+    .                         +-----------+
 *       |           .               +--------+|SimpleEvent|
 *       |           .               |         |Plugin     |
 * +-----|------+    .               v         +-----------+
 * |     |      |    .    +--------------+                    +------------+
 * |     +-----------.--->|EventPluginHub|                    |    Event   |
 * |            |    .    |              |     +-----------+  | Propagators|
 * | ReactEvent |    .    |              |     |TapEvent   |  |------------|
 * |  Emitter   |    .    |              |<---+|Plugin     |  |other plugin|
 * |            |    .    |              |     +-----------+  |  utilities |
 * |     +-----------.--->|              |                    +------------+
 * |     |      |    .    +--------------+
 * +-----|------+    .                ^        +-----------+
 *       |           .                |        |Enter/Leave|
 *       +           .                +-------+|Plugin     |
 * +-------------+   .                         +-----------+
 * | application |   .
 * |-------------|   .
 * |             |   .
 * |             |   .
 * +-------------+   .
 *                   .
 *    React Core     .  General Purpose Event Plugin System
 */

以上内容来自ReactBrowserEventEmitter.js

  • DOM - React通过事件委托机制将大部分(media)事件代理至Document层

  • ReactEventListener - 事件处理器. 在这里进行事件处理器的绑定。当DOM触发事件时,会从这里开始调度分发到React组件树

  • ReactEventEmitter - 暴露接口给React组件层用于添加事件订阅

  • EventPluginHub - 负责管理和注册各种插件。在事件分发时,调用插件来生成合成事件。 React事件系统使用了插件机制来管理不同行为的事件。这些插件会处理自己感兴趣的事件类型,并生成合成事件对象。目前ReactDOM有以下几种插件类型:

    • SimpleEventPlugin - 简单事件,处理一些比较通用的事件类型,同时对不同事件也做了分类。Discrete events(离散事件,如blur、focus、 click、 submit、 touchStart)、User-blocking events(用户阻塞事件,touchMove、mouseMove、scroll、drag、dragOver等)和Continuous events(可连续事件,如load、error、loadStart、abort、animationEnd等,优先级最高);

    • EnterLeaveEventPlugin - mouseEnter/mouseLeave和pointerEnter/pointerLeave这两类事件比较特殊, 和*over/*out事件相比,它们不支持事件冒泡,enter会给所有进入的元素发送事件, 行为有点类似于:hover;而over在进入元素后,还会冒泡通知其上级.

      如果树层次比较深,大量的mouseenter触发可能导致性能问题。另外其不支持冒泡,无法在Document完美的监听和分发,所以ReactDOM使用*over/out事件来模拟这些enter/*leave。

    • ChangeEventPlugin - change事件是React的一个自定义事件,旨在规范化表单元素的变动事件。它支持这些表单元素: input, textarea, select

    • SelectEventPlugin - 和change事件一样,React为表单元素规范化了select(选择范围变动)事件,适用于input、textarea、contentEditable元素.

    • BeforeInputEventPlugin - beforeinput事件以及composition事件处理。

    • TapEventPlugin, 移动端IOS有个著名的300ms点击延迟,不过鉴于这个特性的修复、移除需要很长时间,所以React就不打算去支持了。关于这块的说明可看 react-tap-event-plugin

  • EventPropagators 按照DOM事件传播的两个阶段,遍历React组件树,并收集所有组件的事件处理器.

回到例子

由于大部分合成事件的代理注册的都是冒泡阶段的事件监听器,也就是委托到 document上注册的是冒泡阶段的事件监听器,所以就算显式声明了一个捕获阶段的React事件,例如onClickCapture,此事件的响应也会晚于原生事件的捕获事件以及冒泡事件。实际上,所有原生事件的响应(无论是冒泡事件还是捕获事件),都将早于React合成事件(SyntheticEvent),对原生事件调用e.stopPropagation()将阻止对应SyntheticEvent的响应,因为对应的事件根本无法到达document

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值