初始化
在
react-dom
模块的src/client/ReactDOM
中引入ReactDOMEventHandle
文件,把该文件中的createEventHandle
函数导出为unstable_createEventHandle
以供第三方使用在
ReactDOMEventHandle
文件中引入events/DOMPluginEventSystem
文件,在模块中注册了顶级事件1
2
3
4
5
6// file: packages/react-dom/src/events/DOMPluginEventSystem.js
SimpleEventPlugin.registerEvents()
EnterLeaveEventPlugin.registerEvents()
ChangeEventPlugin.registerEvents()
SelectEventPlugin.registerEvents()
BeforeInputEventPlugin.registerEvents()记录
dom
事件和react
事件的映射关系1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25// file: packages/react-dom/src/events/DOMEventProperties.js
export const topLevelEventsToReactNames = new Map()
// 记录 dom event 和 react event 的映射
function registerSimpleEvent(domEventName, reactName) {
topLevelEventsToReactNames.set(domEventName, reactName)
registerTwoPhaseEvent(reactName, [domEventName])
}
export function registerSimpleEvents() {
for (let i = 0; i < simpleEventPluginEvents.length; i++) {
const eventName = ((simpleEventPluginEvents[i]: any): string)
const domEventName = ((eventName.toLowerCase(): any): DOMEventName)
const capitalizedEvent = eventName[0].toUpperCase() + eventName.slice(1)
registerSimpleEvent(domEventName, 'on' + capitalizedEvent)
}
// Special cases where event names don't match.
registerSimpleEvent(ANIMATION_END, 'onAnimationEnd')
registerSimpleEvent(ANIMATION_ITERATION, 'onAnimationIteration')
registerSimpleEvent(ANIMATION_START, 'onAnimationStart')
registerSimpleEvent('dblclick', 'onDoubleClick')
registerSimpleEvent('focusin', 'onFocus')
registerSimpleEvent('focusout', 'onBlur')
registerSimpleEvent(TRANSITION_END, 'onTransitionEnd')
}在
events/EventRegistry
模块中调用方法把注册的事件放入存储的Set
和对象中,allNativeEvents Set
负责存储所有注册过的dom
事件名称,registrationNameDependencies
以{[reactEventName]: [nativeEventName] }
负责存储注册的事件依赖,同时注册了冒泡事件和捕获事件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25// file: packages/react-dom/src/events/EventRegistry.js
export const allNativeEvents: Set<DOMEventName> = new Set()
/**
* Mapping from registration name to event name
*/
export const registrationNameDependencies = {}
export function registerTwoPhaseEvent(
registrationName: string,
dependencies: Array<DOMEventName>
): void {
registerDirectEvent(registrationName, dependencies)
registerDirectEvent(registrationName + 'Capture', dependencies)
}
export function registerDirectEvent(
registrationName: string,
dependencies: Array<DOMEventName>
) {
registrationNameDependencies[registrationName] = dependencies
for (let i = 0; i < dependencies.length; i++) {
allNativeEvents.add(dependencies[i])
}
}合成事件的初始化完毕
注册原生事件
调用
react-dom
模块中的render
时,若第一次挂载则递归依次调用legacyCreateRootFromDOMContainer
、createLegacyRoot
创建容器对象ReactDOMLegacyRoot
的实例ReactDOMLegacyRoot
递归依次调用createRootImpl
、createContainer
创建了组件挂载的根元素,并表及为根元素调用
listenToAllSupportedEvents
在根元素上挂载事件,若当前容器为注释元素,则取当前元素的父元素为事件挂载的节点在
listenToAllSupportedEvents
中遍历初始化时注册的allNativeEvents
事件列表注册事件,判断根元素是否为documet
节点,不是则获取document
节点,在document
节点节点上注册selectionchange
事件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29// file: packages/react-dom/src/events/DOMPluginEventSystem.js
const listeningMarker = '_reactListening' +Math.random().toString(36).slice(2);
export function listenToAllSupportedEvents(rootContainerElement: EventTarget) {
if (!(rootContainerElement: any)[listeningMarker]) {
rootContainerElement[listeningMarker] = true
allNativeEvents.forEach((domEventName) => {
// We handle selectionchange separately because it
// doesn't bubble and needs to be on the document.
if (domEventName !== 'selectionchange') {
if (!nonDelegatedEvents.has(domEventName)) {
listenToNativeEvent(domEventName, false, rootContainerElement)
}
listenToNativeEvent(domEventName, true, rootContainerElement)
}
})
const 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)
}
}
}
}createEventListenerWrapperWithPriority
函数先获取事件优先级,再根据优先级确定构造监听器的函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27// file: packages/react-dom/src/events/ReactDOMEventListener.js
export function createEventListenerWrapperWithPriority(
targetContainer: EventTarget,
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags
): Function {
const eventPriority = getEventPriority(domEventName)
let listenerWrapper
switch (eventPriority) {
case DiscreteEventPriority:
listenerWrapper = dispatchDiscreteEvent
break
case ContinuousEventPriority:
listenerWrapper = dispatchContinuousEvent
break
case DefaultEventPriority:
default:
listenerWrapper = dispatchEvent
break
}
return listenerWrapper.bind(
null,
domEventName,
eventSystemFlags,
targetContainer
)
}调用
createEventListenerWrapperWithPriority
创建监听函数,根据passive
属性的支持和capture
情况分别调用不同的事件监听方式1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83// file: packages/react-dom/src/events/DOMPluginEventSystem.js
export function listenToNativeEvent(
domEventName: DOMEventName,
isCapturePhaseListener: boolean,
target: EventTarget
): void {
let eventSystemFlags = 0
if (isCapturePhaseListener) {
eventSystemFlags |= IS_CAPTURE_PHASE
}
addTrappedEventListener(
target,
domEventName,
eventSystemFlags,
isCapturePhaseListener
)
}
function addTrappedEventListener(
targetContainer: EventTarget,
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
isCapturePhaseListener: boolean,
isDeferredListenerForLegacyFBSupport?: boolean
) {
let listener = createEventListenerWrapperWithPriority(
targetContainer,
domEventName,
eventSystemFlags
)
// If passive option is not supported, then the event will be
// active and not passive.
let isPassiveListener = undefined
if (passiveBrowserEventsSupported) {
// 是否支持 passive 属性, https://developer.mozilla.org/zh-CN/docs/Web/API/EventTarget/addEventListener
if (
domEventName === 'touchstart' ||
domEventName === 'touchmove' ||
domEventName === 'wheel'
) {
isPassiveListener = true
}
}
targetContainer =
enableLegacyFBSupport && isDeferredListenerForLegacyFBSupport
? (targetContainer: any).ownerDocument
: targetContainer
let unsubscribeListener
// TODO: There are too many combinations here. Consolidate them.
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
)
}
}
}
事件触发
有事件触发时,调用
events/ReactDOMEventListener
下的dispatchEvent
触发事件当事件不可重新触发时直接调用
attemptToDispatchEvent
进行事件的触发,否则事件放入队列等待调用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42// file: packages/react-dom/src/events/ReactDOMEventListener.js
export function dispatchEvent(
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
targetContainer: EventTarget,
nativeEvent: AnyNativeEvent
): void {
const allowReplay = (eventSystemFlags & IS_CAPTURE_PHASE) === 0
if (
allowReplay &&
hasQueuedDiscreteEvents() &&
isReplayableDiscreteEvent(domEventName)
) {
//更新队列中就把事件放入更新队列
queueDiscreteEvent(
null, // Flags that we're not actually blocked on anything as far as we know.
domEventName,
eventSystemFlags,
targetContainer,
nativeEvent
)
return
}
const blockedOn = attemptToDispatchEvent(
domEventName,
eventSystemFlags,
targetContainer,
nativeEvent
)
if (blockedOn === null) {
// 触发事件成功
if (allowReplay) {
clearIfContinuousEvent(domEventName, nativeEvent)
}
return
}
// 其它逻辑
}获取触发事件的元素,然后获取最近的虚拟
dom
节点的实例(向上查找,注:dom
上存储了虚拟dom
的引用,快速查找),通过虚拟dom
节点获取最近挂载的fiber
节点(向上查找)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30// file: packages/react-dom/src/events/ReactDOMEventListener.js
// Attempt dispatching an event. Returns a SuspenseInstance or Container if it's blocked.
export function attemptToDispatchEvent(
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
targetContainer: EventTarget,
nativeEvent: AnyNativeEvent
): null | Container | SuspenseInstance {
const nativeEventTarget = getEventTarget(nativeEvent)
let targetInst = getClosestInstanceFromNode(nativeEventTarget)
if (targetInst !== null) {
const nearestMounted = getNearestMountedFiber(targetInst)
if (nearestMounted === null) {
// 树已经被销毁,触发对象置空
targetInst = null
} else {
// 其它逻辑
}
}
dispatchEventForPluginEventSystem(
domEventName,
eventSystemFlags,
nativeEvent,
targetInst,
targetContainer
)
// We're not blocked on anything.
return null
}在
attemptToDispatchEvent
调用dispatchEventForPluginEventSystem
,找出触发事件元素的根节点实例,然后通过dispatchEventsForPlugins
批量更新1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97// file: packages/react-dom/src/events/DOMPluginEventSystem.js
export function dispatchEventForPluginEventSystem(
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
nativeEvent: AnyNativeEvent,
targetInst: null | Fiber,
targetContainer: EventTarget
): void {
let ancestorInst = targetInst
if (
(eventSystemFlags & IS_EVENT_HANDLE_NON_MANAGED_NODE) === 0 &&
(eventSystemFlags & IS_NON_DELEGATED) === 0
) {
const targetContainerNode = ((targetContainer: any): Node)
if (targetInst !== null) {
// The below logic attempts to work out if we need to change
// the target fiber to a different ancestor. We had similar logic
// in the legacy event system, except the big difference between
// systems is that the modern event system now has an event listener
// attached to each React Root and React Portal Root. Together,
// the DOM nodes representing these roots are the "rootContainer".
// To figure out which ancestor instance we should use, we traverse
// up the fiber tree from the target instance and attempt to find
// root boundaries that match that of our current "rootContainer".
// If we find that "rootContainer", we find the parent fiber
// sub-tree for that root and make that our ancestor instance.
let node = targetInst
mainLoop: while (true) {
if (node === null) {
return
}
const nodeTag = node.tag
if (nodeTag === HostRoot || nodeTag === HostPortal) {
let container = node.stateNode.containerInfo
if (isMatchingRootContainer(container, targetContainerNode)) {
break
}
if (nodeTag === HostPortal) {
// The target is a portal, but it's not the rootContainer we're looking for.
// Normally portals handle their own events all the way down to the root.
// So we should be able to stop now. However, we don't know if this portal
// was part of *our* root.
let grandNode = node.return
while (grandNode !== null) {
const grandTag = grandNode.tag
if (grandTag === HostRoot || grandTag === HostPortal) {
const grandContainer = grandNode.stateNode.containerInfo
if (
isMatchingRootContainer(
grandContainer,
targetContainerNode
)
) {
// This is the rootContainer we're looking for and we found it as
// a parent of the Portal. That means we can ignore it because the
// Portal will bubble through to us.
return
}
}
grandNode = grandNode.return
}
}
// Now we need to find it's corresponding host fiber in the other
// tree. To do this we can use getClosestInstanceFromNode, but we
// need to validate that the fiber is a host instance, otherwise
// we need to traverse up through the DOM till we find the correct
// node that is from the other tree.
while (container !== null) {
const parentNode = getClosestInstanceFromNode(container)
if (parentNode === null) {
return
}
const parentTag = parentNode.tag
if (parentTag === HostComponent || parentTag === HostText) {
node = ancestorInst = parentNode
continue mainLoop
}
container = container.parentNode
}
}
node = node.return
}
}
}
batchedEventUpdates(() =>
dispatchEventsForPlugins(
domEventName,
eventSystemFlags,
nativeEvent,
ancestorInst,
targetContainer
)
)
}dispatchEventsForPlugins
先获取当前Fiber
上绑定的对应事件,然后processDispatchQueue
通过按照顺序processDispatchQueueItemsInOrder
处理各个事件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83// file: packages/react-dom/src/events/DOMPluginEventSystem.js
function dispatchEventsForPlugins(
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
nativeEvent: AnyNativeEvent,
targetInst: null | Fiber,
targetContainer: EventTarget
): void {
const nativeEventTarget = getEventTarget(nativeEvent)
const dispatchQueue: DispatchQueue = []
extractEvents(
dispatchQueue,
domEventName,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
targetContainer
)
processDispatchQueue(dispatchQueue, eventSystemFlags)
}
function extractEvents(
dispatchQueue: DispatchQueue,
domEventName: DOMEventName,
targetInst: null | Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: null | EventTarget,
eventSystemFlags: EventSystemFlags,
targetContainer: EventTarget
) {
SimpleEventPlugin.extractEvents(
dispatchQueue,
domEventName,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
targetContainer
)
// 其它逻辑
}
export function processDispatchQueue(
dispatchQueue: DispatchQueue,
eventSystemFlags: EventSystemFlags
): void {
const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0
for (let i = 0; i < dispatchQueue.length; i++) {
// 取出事件监听函数执行
const { event, listeners } = dispatchQueue[i]
processDispatchQueueItemsInOrder(event, listeners, inCapturePhase)
}
}
function processDispatchQueueItemsInOrder(
event: ReactSyntheticEvent,
dispatchListeners: Array<DispatchListener>,
inCapturePhase: boolean
): void {
let previousInstance
if (inCapturePhase) {
// 捕获事件从后往前执行
for (let i = dispatchListeners.length - 1; i >= 0; i--) {
const { instance, currentTarget, listener } = dispatchListeners[i]
if (instance !== previousInstance && event.isPropagationStopped()) {
return
}
executeDispatch(event, listener, currentTarget)
previousInstance = instance
}
} else {
// 冒泡事件从前往后执行
for (let i = 0; i < dispatchListeners.length; i++) {
const { instance, currentTarget, listener } = dispatchListeners[i]
if (instance !== previousInstance && event.isPropagationStopped()) {
return
}
executeDispatch(event, listener, currentTarget)
previousInstance = instance
}
}
}extractEvents
提取事件监听函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76// file: packages/react-dom/src/events/plugins/SimpleEventPlugin.js
function extractEvents(
dispatchQueue: DispatchQueue,
domEventName: DOMEventName,
targetInst: null | Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: null | EventTarget,
eventSystemFlags: EventSystemFlags,
targetContainer: EventTarget
): void {
const reactName = topLevelEventsToReactNames.get(domEventName)
if (reactName === undefined) {
return
}
let SyntheticEventCtor = SyntheticEvent
let reactEventType: string = domEventName
// 根据事件类型获取对应的构造函数
const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0
if (
enableCreateEventHandleAPI &&
eventSystemFlags & IS_EVENT_HANDLE_NON_MANAGED_NODE
) {
const listeners = accumulateEventHandleNonManagedNodeListeners(
// TODO: this cast may not make sense for events like
// "focus" where React listens to e.g. "focusin".
((reactEventType: any): DOMEventName),
targetContainer,
inCapturePhase
)
if (listeners.length > 0) {
// Intentionally create event lazily.
const event = new SyntheticEventCtor(
reactName,
reactEventType,
null,
nativeEvent,
nativeEventTarget
)
dispatchQueue.push({ event, listeners })
}
} else {
// Some events don't bubble in the browser.
// In the past, React has always bubbled them, but this can be surprising.
// We're going to try aligning closer to the browser behavior by not bubbling
// them in React either. We'll start by not bubbling onScroll, and then expand.
const accumulateTargetOnly =
!inCapturePhase &&
// TODO: ideally, we'd eventually add all events from
// nonDelegatedEvents list in DOMPluginEventSystem.
// Then we can remove this special list.
// This is a breaking change that can wait until React 18.
domEventName === 'scroll'
const listeners = accumulateSinglePhaseListeners(
targetInst,
reactName,
nativeEvent.type,
inCapturePhase,
accumulateTargetOnly,
nativeEvent
)
if (listeners.length > 0) {
// Intentionally create event lazily.
const event = new SyntheticEventCtor(
reactName,
reactEventType,
null,
nativeEvent,
nativeEventTarget
)
dispatchQueue.push({ event, listeners })
}
}
}调用
accumulateSinglePhaseListeners
获取事件监听列表,从触发元素递归向上查询注册的事件然后放入监听器列表中返回
1 | // file: packages/react-dom/src/events/DOMPluginEventSystem.js |