react-事件系统2-v16/17/18事件系统

loading 2023年01月08日 119次浏览

这篇的事件注册和事件触发最好结合着事件系统0去看。

1. 事件池

上回提到,React为了避免垃圾回收,因而引入了事件池的概念,从而防止事件会被频繁的创建和回收。

从本质上来讲,事件池是React提供的一种优化方式,将所有的合成事件都放到事件池内统一管理,同时不同类型的合成事件对应不同的事件池

拓展一个小问题:合成对象e如何持久化?

    <input onChange={(e) => {
      console.log(e.target) //<input>
      setTimeout(() => {
        console.log(e.target) //null
      })
    }} />

上面代码中,按理来说两处e.target应该一致,然而第二个e.target只能打印null,为什么呢?

这是因为每次在派发事件中,React都会从事件池中判断,是否能够复用,当派发完成时,就会将函数的属性置成null,也就是会清空对应的属性,所以setTimeout会打印出null的原因。

我们需要通过e.persistent() 来实现对象的持久化,可以使得onChange不进入事件池,因此也就不会被清空销毁,得以保留e的值。

2. 事件绑定(事件注册)

我们知道react中所有事件都是模拟的,那么react如何将模拟的事件进行绑定呢?

2.1 事件插件

在React中,所有的事件都是通过插件来进行统一处理,但并非是同一个插件,因为每个事件的处理逻辑、事件源都不同,所以会有多个事件插件。

如:onClick对应SimpleEventPlugin、onChange对应ChangeEventPlugin

这里介绍三个比较典型的插件:

  • SimpleEventPlugin: 这个插件比较通用,大多数方法都是通过此插件处理,如:click、input、focus等,与原生事件一一对应,所以这类事件比较好处理
  • EnterLeaveEventPlugin: onMouseEnter和onMouseLeave依靠于mouseout和mouseover原生事件,这样可以在document上面进行委托监听
  • ChangeEventPlugin: 在React中,onChange比较特殊,它是React的一个自定义的事件,它依赖8种原生事件来模拟onChange事件

2.2 事件绑定

  • 在React中,首先将虚拟dom转化成fiber,在fiber中的props如果是合成事件(如:onClick),就会按照独立的处理逻辑,单独处理
  • 然后根据合成事件的事件类型,寻找对应的原生事件
  • 之后会判断事件类型,大多数事件(onClick)都是走的冒泡逻辑,少部分事件(如:onScroll)会走捕获逻辑
  • 最后会调用trapEventForPluginEventSystem函数,绑定在document上,实现统一处理函数(dispatchEvent函数)

3. 事件触发

  • 所有的事件首先通过dispatchEvent函数处理,然后进行批量更新
  • 然后根据事件源event找到与之匹配的DOM元素fiber,进入插件中的extractEvents,然后遍历,得到最终的一个队列,这个队列就是React用来模拟的事件过程
  • 最终走向runEventsInBatch,进行批量执行事件队列,完成整个触发流程。如果发现有阻止冒泡的情况,则会跳出循环,重置事件源,再放回到事件池中,完成流程

4. v17/18事件系统

4.1 事件绑定

首先,在v17版本中,将顶层事件调整到container上,这样做的目的主要是为了:兼容性和跨平台,可以兼容多个版本,非常有利于微前端(微前端会对应多个系统,存在对应多个react版本的问题)

同时在React v16中,React执行大多数事件都会调用documnet.addEventListener(), 而在v17中,在底层中调用rootNode.addEventListener()

4.2 取消事件池

image.png

简单的说,就是没啥用,也没有对应的性能提高,所以就没了,但去除事件池后,自然也不存在持久化的问题,所以在setTimeout可以获得对应的事件源。

4.3 捕获事件

v16中不存在真正的捕获事件,是冒泡到document后模拟出来的,而v17/18真正存在。详情还是看事件系统0里有讲。