React SyntheticEvent
合成事件
合成事件
React 17.0.2
React 在文档中提到:
SyntheticEvent 实例将被传递给你的事件处理函数,它是浏览器的原生事件的跨浏览器包装器。
在 React 中,并不像 DOM 一样在各个节点上注册事件,而是在根节点监听事件。然后通过事件捕获、事件冒泡的机制响应事件回调方法。
针对合成事件的处理可以理解为两个阶段:
在构建 fiberRoot 时通过 listenToAllSupportedEvents
为 fiberRoot 添加事件监听,同时对部分特殊事件有特别的处理逻辑。
function createRootImpl(
container: Container,
tag: RootTag,
options: void | RootOptions
) {
// 对根节点添加监听事件
listenToAllSupportedEvents(rootContainerElement)
return root
}
function listenToAllSupportedEvents(rootContainerElement: EventTarget) {
if (!(rootContainerElement: any)[listeningMarker]) {
;(rootContainerElement: any)[listeningMarker] = true
allNativeEvents.forEach(domEventName => {
if (domEventName !== 'selectionchange') {
if (!nonDelegatedEvents.has(domEventName)) {
listenToNativeEvent(domEventName, false, rootContainerElement)
}
// listenToNativeEvent 是对 addEventListener 的封装
// 可以理解为 addEventListener
listenToNativeEvent(domEventName, true, rootContainerElement)
}
})
}
}
而绑定到事件的回调函数,根据事件优先级的不同设置不同的回调函数(默认为 dispatchEvent),并返回作为 listener 绑定到根节点上。
// listenToNativeEvent -> addTrappedEventListener
function addTrappedEventListener(
targetContainer: EventTarget,
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
isCapturePhaseListener: boolean,
isDeferredListenerForLegacyFBSupport?: boolean,
) {
// 生成响应函数
let listener = createEventListenerWrapperWithPriority(
targetContainer,
domEventName,
eventSystemFlags,
)
// 给 target 添加监听方法,并返回取消监听方法
unsubscribeListener = addEventBubbleListener(targetContainer, domEventName, listener)
}
ReactDOM 会在初始化的时候,调用各种 EventPlugin.registerEvents 来注册当前环境(如浏览器)应该处理的事件名称。
SimpleEventPlugin.registerEvents()
EnterLeaveEventPlugin.registerEvents()
ChangeEventPlugin.registerEvents()
SelectEventPlugin.registerEvents()
BeforeInputEventPlugin.registerEvents()
经过处理后的 registrationNameDependencies 记录了当前环境 SyntheticEvent 对应的 NativeEvent 映射关系。
registrationNameDependencies = {
// ...
onChange: [
'change',
'click',
'focusin',
'focusout',
'input',
'keydown',
'keyup',
'selectionchange',
],
onChangeCapture: [
'change',
'click',
'focusin',
'focusout',
'input',
'keydown',
'keyup',
'selectionchange',
],
onClick: ['click'],
onClickCapture: ['click'],
// ...
}
经过 listenToAllSupportedEvents 方法处理后,可以支持任何在根节点内触发的被当前运行环境支持的事件。以
click 事件为例整体流程如下:
dispatchEvent 中有个 findInstanceBlockingEvent 方法,目的是在有多个 Root 节点的应用中,将一个 Root 内触发的事件拦截在该 Root 中不再往上冒泡。
dispatchEventForPluginEventSystem 中的 mainLoop 方法?
dispatchEventsForPlugins 通过 nativeEvent.target || nativeEvent.srcElement
获取事件触发的 DOM 节点,由于我们以 click 事件为例,click 对应的 dispatchEvent 会对应到
SimpleEventPlugin.extractEvents。该方法根据 domEventName 获取对应的合成事件类,如 click 对应为
SyntheticMouseEvent 类,并构建实例 event。
同时,会一直遍历到根结点,检查该事件影响到的节点是否有对应的 reactEventName 属性,如 click 对应的 onClick 属性,将所有的节点及 reactEventName 记录到 listeners,一起添加到 dispatchQueue 中
const listeners = accumulateSinglePhaseListeners()
if (listeners.length > 0) {
const event = new SyntheticEventCtor()
dispatchQueue.push({ event, listeners })
}
然后调用 processDispatchQueue 遍历 dispatchQueue 队列,对 listeners 依次调用。