Synthetic Events
- Events delegation:
- React 16: delegate events handlers on
documentDOM node. - React 17: delegate events handlers on
approot DOM node. - 先处理原生事件, 后处理 React 事件.
- React 16: delegate events handlers on
- Events dispatching: dispatch native events to
React.onXXXhandlers bySyntheticEvent.- 收集监听器:
const listeners = accumulateSinglePhaseListeners(targetFiber, eventName). - 派发合成事件:
dispatchQueue.push({ new SyntheticEvent(eventName), listeners }). - 执行派发:
processDispatchQueue(dispatchQueue, eventSystemFlags)->executeDispatch(event, listener, currentTarget). - Capture event: 从上至下调用 Fiber 树中绑定的回调函数.
- Bubble event: 从下至上调用 Fiber 树中绑定的回调函数.
- 收集监听器:
react-dom/src/events/DOMPluginEventSystem:
function listenToAllSupportedEvents(rootContainerElement: EventTarget) {
if (enableEagerRootListeners) {
// 1. 节流优化, 保证全局注册只被调用一次.
if (rootContainerElement[listeningMarker])
return
rootContainerElement[listeningMarker] = true
// 2. 遍历 allNativeEvents 监听冒泡和捕获阶段的事件.
allNativeEvents.forEach((domEventName) => {
if (!nonDelegatedEvents.has(domEventName)) {
listenToNativeEvent(
domEventName,
false, // 冒泡阶段监听.
rootContainerElement,
null,
)
}
listenToNativeEvent(
domEventName,
true, // 捕获阶段监听.
rootContainerElement,
null,
)
})
}
}
function listenToNativeEvent(
domEventName: DOMEventName,
isCapturePhaseListener: boolean,
rootContainerElement: EventTarget,
targetElement: Element | null,
eventSystemFlags?: EventSystemFlags = 0,
): void {
const target = rootContainerElement
const listenerSet = getEventListenerSet(target)
const listenerSetKey = getListenerSetKey(domEventName, isCapturePhaseListener)
// 利用 Set 数据结构, 保证相同的事件类型只会被注册一次.
if (!listenerSet.has(listenerSetKey)) {
if (isCapturePhaseListener)
eventSystemFlags |= IS_CAPTURE_PHASE
// 注册事件监听.
addTrappedEventListener(
target,
domEventName,
eventSystemFlags,
isCapturePhaseListener,
)
listenerSet.add(listenerSetKey)
}
}
function addTrappedEventListener(
targetContainer: EventTarget,
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
isCapturePhaseListener: boolean,
isDeferredListenerForLegacyFBSupport?: boolean,
) {
// 1. 构造 listener.
const listener = createEventListenerWrapperWithPriority(
targetContainer,
domEventName,
eventSystemFlags,
)
// 2. 注册事件监听.
let unsubscribeListener
if (isCapturePhaseListener) {
unsubscribeListener = addEventCaptureListener(
targetContainer,
domEventName,
listener,
)
} else {
unsubscribeListener = addEventBubbleListener(
targetContainer,
domEventName,
listener,
)
}
}
// 注册原生冒泡事件.
function addEventBubbleListener(
target: EventTarget,
eventType: string,
listener: Function,
): Function {
target.addEventListener(eventType, listener, false)
return listener
}
// 注册原生捕获事件.
function addEventCaptureListener(
target: EventTarget,
eventType: string,
listener: Function,
): Function {
target.addEventListener(eventType, listener, true)
return listener
}
react-dom/src/events/ReactDOMEventListener:
// 派发原生事件至 React.onXXX.
function createEventListenerWrapperWithPriority(
targetContainer: EventTarget,
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
): Function {
// 1. 根据优先级设置 listenerWrapper.
const eventPriority = getEventPriorityForPluginSystem(domEventName)
let listenerWrapper
switch (eventPriority) {
case DiscreteEvent:
listenerWrapper = dispatchDiscreteEvent
break
case UserBlockingEvent:
listenerWrapper = dispatchUserBlockingUpdate
break
case ContinuousEvent:
default:
listenerWrapper = dispatchEvent
break
}
// 2. 返回 listenerWrapper.
return listenerWrapper.bind(
null,
domEventName,
eventSystemFlags,
targetContainer,
)
}
function dispatchDiscreteEvent(
domEventName,
eventSystemFlags,
container,
nativeEvent,
) {
const previousPriority = getCurrentUpdatePriority()
const prevTransition = ReactCurrentBatchConfig.transition
ReactCurrentBatchConfig.transition = null
try {
setCurrentUpdatePriority(DiscreteEventPriority)
dispatchEvent(domEventName, eventSystemFlags, container, nativeEvent)
} finally {
setCurrentUpdatePriority(previousPriority)
ReactCurrentBatchConfig.transition = prevTransition
}
}
function dispatchContinuousEvent(
domEventName,
eventSystemFlags,
container,
nativeEvent,
) {
const previousPriority = getCurrentUpdatePriority()
const prevTransition = ReactCurrentBatchConfig.transition
ReactCurrentBatchConfig.transition = null
try {
setCurrentUpdatePriority(ContinuousEventPriority)
dispatchEvent(domEventName, eventSystemFlags, container, nativeEvent)
} finally {
setCurrentUpdatePriority(previousPriority)
ReactCurrentBatchConfig.transition = prevTransition
}
}
function dispatchEvent(
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
targetContainer: EventTarget,
nativeEvent: AnyNativeEvent,
) {
let blockedOn = findInstanceBlockingEvent(
domEventName,
eventSystemFlags,
targetContainer,
nativeEvent,
)
if (blockedOn === null) {
dispatchEventForPluginEventSystem(
domEventName,
eventSystemFlags,
nativeEvent,
return_targetInst,
targetContainer,
)
clearIfContinuousEvent(domEventName, nativeEvent)
return
}
if (
queueIfContinuousEvent(
blockedOn,
domEventName,
eventSystemFlags,
targetContainer,
nativeEvent,
)
) {
nativeEvent.stopPropagation()
return
}
// We need to clear only if we didn't queue because queueing is accumulative.
clearIfContinuousEvent(domEventName, nativeEvent)
if (
eventSystemFlags & IS_CAPTURE_PHASE
&& isDiscreteEventThatRequiresHydration(domEventName)
) {
while (blockedOn !== null) {
const fiber = getInstanceFromNode(blockedOn)
if (fiber !== null)
attemptSynchronousHydration(fiber)
const nextBlockedOn = findInstanceBlockingEvent(
domEventName,
eventSystemFlags,
targetContainer,
nativeEvent,
)
if (nextBlockedOn === null) {
dispatchEventForPluginEventSystem(
domEventName,
eventSystemFlags,
nativeEvent,
return_targetInst,
targetContainer,
)
}
if (nextBlockedOn === blockedOn)
break
blockedOn = nextBlockedOn
}
if (blockedOn !== null)
nativeEvent.stopPropagation()
return
}
dispatchEventForPluginEventSystem(
domEventName,
eventSystemFlags,
nativeEvent,
null,
targetContainer,
)
}
