一、事件注册
入口
registerSimpleEvents();
registerEvents$2();
registerEvents$1();
registerEvents$3();
registerEvents();
registerSimpleEvent
- 将dom事件名和react事件名映射到topLevelEventsToReactNames集合
- 调用registerTwoPhaseEvent函数
var topLevelEventsToReactNames = new Map();
// domEventName dom事件名
// reactName React事件名
function registerSimpleEvent(domEventName, reactName) {
topLevelEventsToReactNames.set(domEventName, reactName);
registerTwoPhaseEvent(reactName, [domEventName]);
}
registerTwoPhaseEvent
注册冒泡事件和捕获事件
- React官方文档对
Capture有如下解释 如需注册捕获阶段的事件处理函数,则应为事件名添加Capture。例如,处理捕获阶段的点击事件请使用onClickCapture,而不是onClick。
function registerTwoPhaseEvent(registrationName, dependencies) {
registerDirectEvent(registrationName, dependencies);
// 在React事件名称后加上Capture表示捕获事件
registerDirectEvent(registrationName + "Capture", dependencies);
}
registerDirectEvent
// 原生事件名的集合
var allNativeEvents = new Set();
// registrationNameDependencies 负责存储{React事件名:[原生事件名]}的映射
var registrationNameDependencies = {};
// possibleRegistrationNames 负责存储{原生事件名:React事件名}的映射
var possibleRegistrationNames = {}
// registrationName React事件名
// 原生DOM事件名 dependencies
function registerDirectEvent(registrationName, dependencies) {
{
// 如果registrationNameDependencies事件已经存在了 说明事件系统可能被重复注册
if (registrationNameDependencies[registrationName]) {
error(
"EventRegistry: More than one plugin attempted to publish the same " +
"registration name, `%s`.",
registrationName
);
}
}
// 将事件的依赖关系存入到 registrationNameDependencies
registrationNameDependencies[registrationName] = dependencies;
{
var lowerCasedName = registrationName.toLowerCase();
possibleRegistrationNames[lowerCasedName] = registrationName;
if (registrationName === "onDoubleClick") {
possibleRegistrationNames.ondblclick = registrationName;
}
}
// 遍历dependencies 并将事件名放入allNativeEvents中
for (var i = 0; i < dependencies.length; i++) {
allNativeEvents.add(dependencies[i]);
}
}
- 我们看来下注册事件后全局状态的数据模型
- allNativeEvents
- registrationNameDependencies
- possibleRegistrationNames
事件绑定
listenToAllSupportedEvents
- 绑定所有的事件
- 执行listenToNativeEvent
// rootContainerElement 根元素
function listenToAllSupportedEvents(rootContainerElement) {
// listeningMarker 是否加载事件的标记
// 可以在原生dom上看到该方法 如下图
// 当注册之后 标记为true 下次再执行该方法时 直接跳过
if (!rootContainerElement[listeningMarker]) {
rootContainerElement[listeningMarker] = true;
// 遍历allNativeEvents
allNativeEvents.forEach(function (domEventName) {
// selectionchange事件因为不冒泡 所以需要单独处理
if (domEventName !== "selectionchange") {
// nonDelegatedEvents 非捕获的事件集合
// 如果该事件是非捕获的,直接冒泡
if (!nonDelegatedEvents.has(domEventName)) {
listenToNativeEvent(domEventName, false, rootContainerElement);
}
listenToNativeEvent(domEventName, true, rootContainerElement);
}
});
// 处理 selectionchange 事件
var ownerDocument =
rootContainerElement.nodeType === DOCUMENT_NODE
? rootContainerElement
: rootContainerElement.ownerDocument;
if (ownerDocument !== null) {
// The selectionchange event also needs deduplication
// but it is attached to the document.
if (!ownerDocument[listeningMarker]) {
ownerDocument[listeningMarker] = true;
listenToNativeEvent("selectionchange", false, ownerDocument);
}
}
}
}
root上的listening标记
listenToNativeEvent
- 计算eventSystemFlags
- 执行addTrappedEventListener
// domEventName dom事件名
// isCapturePhaseListener 是否是冒泡
// target 根dom元素
function listenToNativeEvent(domEventName, isCapturePhaseListener, target) {
{
// 对特殊情况进行抛错
if (nonDelegatedEvents.has(domEventName) && !isCapturePhaseListener) {
error(
'Did not expect a listenToNativeEvent() call for "%s" in the bubble phase. ' +
"This is a bug in React. Please file an issue.",
domEventName
);
}
}
var eventSystemFlags = 0;
if (isCapturePhaseListener) {
// IS_CAPTURE_PHASE 表示2进制的 0b100
// 当我们判断是否是冒泡时候可以直接使用二进制进行计算
eventSystemFlags |= IS_CAPTURE_PHASE;
}
addTrappedEventListener(
target,
domEventName,
eventSystemFlags,
isCapturePhaseListener
);
}
addTrappedEventListener
- 执行createEventListenerWrapperWithPriority创建具有优先级的监听器
- 根据passave和是否冒泡执行不同的添加事件函数addEventCaptureListenerWithPassiveFlag、addEventCaptureListener、addEventBubbleListenerWithPassiveFlag、addEventBubbleListener
// targetContainer 根DOM元素
// domEventName 事件名
// eventSystemFlags 事件系统标识
// isCapturePhaseListener // 是否是捕获的listener
function addTrappedEventListener(
targetContainer,
domEventName,
eventSystemFlags,
isCapturePhaseListener
) {
// 创建具有优先级的listenner
var listener = createEventListenerWrapperWithPriority(
targetContainer,
domEventName,
eventSystemFlags
); // If passive option is not supported, then the event will be
// active and not passive.
var isPassiveListener = undefined;
// 处理passivepassive带来的bug // 可以忽略
if (passiveBrowserEventsSupported) {
// Browsers introduced an intervention, making these events
// passive by default on document. React doesn't bind them
// to document anymore, but changing this now would undo
// the performance wins from the change. So we emulate
// the existing behavior manually on the roots now.
// https://github.com/facebook/react/issues/19651
if (
domEventName === "touchstart" ||
domEventName === "touchmove" ||
domEventName === "wheel"
) {
isPassiveListener = true;
}
}
targetContainer = targetContainer;
var unsubscribeListener; // When legacyFBSupport is enabled, it's for when we
if (isCapturePhaseListener) {
if (isPassiveListener !== undefined) {
unsubscribeListener = addEventCaptureListenerWithPassiveFlag(
targetContainer,
domEventName,
listener,
isPassiveListener
);
} else {
unsubscribeListener = addEventCaptureListener(
targetContainer,
domEventName,
listener
);
}
} else {
if (isPassiveListener !== undefined) {
unsubscribeListener = addEventBubbleListenerWithPassiveFlag(
targetContainer,
domEventName,
listener,
isPassiveListener
);
} else {
unsubscribeListener = addEventBubbleListener(
targetContainer,
domEventName,
listener
);
}
}
}
createEventListenerWrapperWithPriority
- 通过getEventPriority获取事件的优先级
getEventPriority
- React18中将事件的优先级分为DiscreteEventPriority、ContinuousEventPriority、DefaultEventPriority、IdleEventPriority、ContinuousEventPriority 根据事件获取优先级
function getEventPriority(domEventName) {
switch (domEventName) {
// Used by SimpleEventPlugin:
case "cancel":
case "click":
case "close":
case "contextmenu":
case "copy":
case "cut":
case "auxclick":
case "dblclick":
case "dragend":
case "dragstart":
case "drop":
case "focusin":
case "focusout":
case "input":
case "invalid":
case "keydown":
case "keypress":
case "keyup":
case "mousedown":
case "mouseup":
case "paste":
case "pause":
case "play":
case "pointercancel":
case "pointerdown":
case "pointerup":
case "ratechange":
case "reset":
case "resize":
case "seeked":
case "submit":
case "touchcancel":
case "touchend":
case "touchstart":
case "volumechange": // Used by polyfills:
// eslint-disable-next-line no-fallthrough
case "change":
case "selectionchange":
case "textInput":
case "compositionstart":
case "compositionend":
case "compositionupdate": // Only enableCreateEventHandleAPI:
// eslint-disable-next-line no-fallthrough
case "beforeblur":
case "afterblur": // Not used by React but could be by user code:
// eslint-disable-next-line no-fallthrough
case "beforeinput":
case "blur":
case "fullscreenchange":
case "focus":
case "hashchange":
case "popstate":
case "select":
case "selectstart":
return DiscreteEventPriority;
case "drag":
case "dragenter":
case "dragexit":
case "dragleave":
case "dragover":
case "mousemove":
case "mouseout":
case "mouseover":
case "pointermove":
case "pointerout":
case "pointerover":
case "scroll":
case "toggle":
case "touchmove":
case "wheel": // Not used by React but could be by user code:
// eslint-disable-next-line no-fallthrough
case "mouseenter":
case "mouseleave":
case "pointerenter":
case "pointerleave":
return ContinuousEventPriority;
case "message": {
// We might be in the Scheduler callback.
// Eventually this mechanism will be replaced by a check
// of the current priority on the native scheduler.
var schedulerPriority = getCurrentPriorityLevel();
switch (schedulerPriority) {
case ImmediatePriority:
return DiscreteEventPriority;
case UserBlockingPriority:
return ContinuousEventPriority;
case NormalPriority:
case LowPriority:
// TODO: Handle LowSchedulerPriority, somehow. Maybe the same lane as hydration.
return DefaultEventPriority;
case IdlePriority:
return IdleEventPriority;
default:
return DefaultEventPriority;
}
}
default:
return DefaultEventPriority;
}
}
addEventCaptureListenerWithPassiveFlag addEventCaptureListener addEventBubbleListenerWithPassiveFlag addEventBubbleListener
- 根据不同的类型绑定监听函数
function addEventBubbleListener(target, eventType, listener) {
target.addEventListener(eventType, listener, false);
return listener;
}
function addEventCaptureListener(target, eventType, listener) {
target.addEventListener(eventType, listener, true);
return listener;
}
function addEventCaptureListenerWithPassiveFlag(
target,
eventType,
listener,
passive
) {
target.addEventListener(eventType, listener, {
capture: true,
passive: passive,
});
return listener;
}
function addEventBubbleListenerWithPassiveFlag(
target,
eventType,
listener,
passive
) {
target.addEventListener(eventType, listener, {
passive: passive,
});
return listener;
}