Skip to content

EnterLeaveEventPlugin

SelectEventPlugin 是 React 中处理 onMouseEnter , onMouseLeave , onPointerEnter ,onPointerLeave 事件的插件。

因为对于这一类事件的原生事件其存在向子元素传播的问题,此插件主要就是解决这个问题。

其跟 ChangeEventPlugin 类似,通过 2 种不同的事件类型来模拟 上述事件的触发。

EnterLeaveEventPlugin 事件的注册

js
function registerEvents() {
  registerDirectEvent("onMouseEnter", ["mouseout", "mouseover"]);
  registerDirectEvent("onMouseLeave", ["mouseout", "mouseover"]);
  registerDirectEvent("onPointerEnter", ["pointerout", "pointerover"]);
  registerDirectEvent("onPointerLeave", ["pointerout", "pointerover"]);
}

那么当我们在一个 div 的元素上定义了 onMouseEnter, 那么在初始化的时候就会增加了 mouseoutmouseover 这两个事件的监听。当事件触发的时候就进入了 extractEvents 流程。

那么其是怎么去解决 事件向子元素传播的问题的呢?

对于这个问题首先需要了解一下对于 mouseout 和 mouseover 事件中比较特殊的属性

  • relatedTarget

指向当前元素的鼠标事件的相关元素。如果是 over 类型的事件,relatedTarget 指向的是鼠标离开的元素;如果是 out 类型的事件,relatedTarget 指向的是鼠标进入的元素。

所以在事件触发的时候就可以通过 targetInstnativeEvent.relatedTarget || (nativeEvent: any).toElement 这两个属性知道当前 离开的节点 和 进入的节点(即 from 和 to )两个对象,然后通过 nativeTargetInst === targetInst 去判断当前事件是否在当前元素上触发,还是在子元素上触发的,从而避免了事件向子元素传播的问题。

  1. 获取两个相关的 节点属性对象 (from 和 to)
js
// 获取 over 和 out 类型事件的 from  和 to 节点
//  out 事件:  from 是当前事件目标节点,to 是获取光标的目标节点
//  over 事件: from 是获取光标节点,to 是当前事件目标节点
let from;
let to;
if (isOutEvent) {
  // 获取当前事件目标节点的相关元素节点,
  // 对于 out 事件来说,relatedTarget 就是获取光标的目标节点,
  //                  nativeEvent : 就是失去光标的目标节点对象
  const related = nativeEvent.relatedTarget || (nativeEvent: any).toElement;
  from = targetInst;
  // 获取获取光标节点的最近的挂载节点
  to = related ? getClosestInstanceFromNode((related: any)) : null;
  if (to !== null) {
    // 找到最近一个已挂载的节点
    const nearestMounted = getNearestMountedFiber(to);
    // 如果目标节点不是最近已挂载的节点,那么将目标节点设置为 null
    // 从而防止事件发生在未挂载的节点上
    if (
      to !== nearestMounted ||
      (to.tag !== HostComponent && to.tag !== HostText)
    ) {
      to = null;
    }
  }
} else {
  // Moving to a node from outside the window.
  // 对于 over 事件来说,from 就是获取光标节点,
  from = null;
  to = targetInst;
}

其中对于 out 事件来说,relatedTarget 就是获取光标的目标节点,nativeEvent : 就是失去光标的目标节点对象,然后通过 getClosestInstanceFromNode(related)来获取光标节点的最近的挂载节点,从而避免了事件只处理已挂载的组件,这样对于 进入事件

  • 进入事件
    • from : null
    • to : 当前事件目标节点
  • 离开事件
    • from : 当前事件目标节点
    • to : 获取光标节点(光标节点的最近的挂载节点)
  1. 避免事件向子元素传播

通过 if (nativeTargetInst === targetInst) 来判断当前事件是否在当前元素上触发,还是在子元素上触发的,从而避免了事件向子元素传播的问题。

总结

  • 对于 mouseover 事件,会检查 relatedTarget 是否也是 React 管理的元素,避免重复派发
  • 只处理最外层祖先的 enter 事件,防止事件在子元素间重复触发
  • 支持鼠标事件和指针事件的统一处理
  • 通过 isReplayingEvent 检查避免重复处理事件
  • 通过 getNearestMountedFiber 确保只处理已挂载的组件
  • 当 from 和 to 相同时直接返回,避免不必要的事件处理