Appearance
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, 那么在初始化的时候就会增加了 mouseout 和 mouseover 这两个事件的监听。当事件触发的时候就进入了 extractEvents 流程。
那么其是怎么去解决 事件向子元素传播的问题的呢?
对于这个问题首先需要了解一下对于 mouseout 和 mouseover 事件中比较特殊的属性
- relatedTarget
指向当前元素的鼠标事件的相关元素。如果是 over 类型的事件,relatedTarget 指向的是鼠标离开的元素;如果是 out 类型的事件,relatedTarget 指向的是鼠标进入的元素。
所以在事件触发的时候就可以通过 targetInst 和 nativeEvent.relatedTarget || (nativeEvent: any).toElement 这两个属性知道当前 离开的节点 和 进入的节点(即 from 和 to )两个对象,然后通过 nativeTargetInst === targetInst 去判断当前事件是否在当前元素上触发,还是在子元素上触发的,从而避免了事件向子元素传播的问题。
- 获取两个相关的 节点属性对象 (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 : 获取光标节点(光标节点的最近的挂载节点)
- 避免事件向子元素传播
通过 if (nativeTargetInst === targetInst) 来判断当前事件是否在当前元素上触发,还是在子元素上触发的,从而避免了事件向子元素传播的问题。
总结
- 对于 mouseover 事件,会检查 relatedTarget 是否也是 React 管理的元素,避免重复派发
- 只处理最外层祖先的 enter 事件,防止事件在子元素间重复触发
- 支持鼠标事件和指针事件的统一处理
- 通过 isReplayingEvent 检查避免重复处理事件
- 通过 getNearestMountedFiber 确保只处理已挂载的组件
- 当 from 和 to 相同时直接返回,避免不必要的事件处理
