ContinuousEvent连续事件,例如load、error、loadStart、abort、animationEnd,这个优先级最高,也就是说它们应该是立即同步执行的,这就是Continuous的意义,是持续地执行,不能被打断。
此外React将事件系统用到了Fiber架构里,Fiber中将任务分成了5大类,对应不同的优先级,那么三大类的事件系统和五大类的Fiber任务系统的对应关系如下。
Immediate: 此类任务会同步执行,或者说马上执行且不能中断,ContinuousEvent便属于此类。
UserBlocking: 此类任务一般是用户交互的结果,需要及时得到反馈,DiscreteEvent与UserBlockingEvent都属于此类。
Normal: 此类任务是应对那些不需要立即感受到反馈的任务,比如网络请求。
Low: 此类任务可以延后处理,但最终应该得到执行,例如分析通知。
Idle: 此类任务的定义为没有必要做的任务。
回到trapEventForPluginEventSystem,实际上在这三类事件,他们最终都会有统一的触发函数dispatchEvent,只不过在dispatch之前会需要进行一些特殊的处理。
// packages\react-dom\src\events\ReactDOMEventListener.js line 256 function trapEventForPluginEventSystem( element: Document | Element | Node, topLevelType: DOMTopLevelEventType, capture: boolean, ): void { let listener; switch (getEventPriority(topLevelType)) { case DiscreteEvent: listener = dispatchDiscreteEvent.bind( null, topLevelType, PLUGIN_EVENT_SYSTEM, ); break; case UserBlockingEvent: listener = dispatchUserBlockingUpdate.bind( null, topLevelType, PLUGIN_EVENT_SYSTEM, ); break; case ContinuousEvent: default: // 统一的分发函数 dispatchEvent listener = dispatchEvent.bind(null, topLevelType, PLUGIN_EVENT_SYSTEM); break; } const rawEventName = getRawEventName(topLevelType); if (capture) { // 注册捕获事件 addEventCaptureListener(element, rawEventName, listener); } else { // 注册冒泡事件 addEventBubbleListener(element, rawEventName, listener); } }到达最终的事件注册,实际上就是在document上注册了各种事件。
// packages\react-dom\src\events\EventListener.js line 10 export function addEventBubbleListener( element: Document | Element | Node, eventType: string, listener: Function, ): void { element.addEventListener(eventType, listener, false); } export function addEventCaptureListener( element: Document | Element | Node, eventType: string, listener: Function, ): void { element.addEventListener(eventType, listener, true); } export function addEventCaptureListenerWithPassiveFlag( element: Document | Element | Node, eventType: string, listener: Function, passive: boolean, ): void { element.addEventListener(eventType, listener, { capture: true, passive, }); } 事件存储让我们回到上边的listenToTopLevel方法中的listeningSet.add(topLevelType),即是将事件添加到注册到事件列表对象中,即将DOM节点和对应的事件保存到Weak Map对象中,具体来说就是DOM节点作为键名,事件对象的Set作为键值,这里的数据集合有自己的名字叫做EventPluginHub,当然在这里最理想的情况会是使用WeakMap进行存储,不支持则使用Map对象,使用WeakMap主要是考虑到WeakMaps保持了对键名所引用的对象的弱引用,不用担心内存泄漏问题,WeakMaps应用的典型场合就是DOM节点作为键名。
// packages\react-dom\src\events\ReactBrowserEventEmitter.js line 88 const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map; const elementListeningSets: | WeakMap | Map< Document | Element | Node, Set<DOMTopLevelEventType | string>, > = new PossiblyWeakMap(); export function getListeningSetForElement( element: Document | Element | Node, ): Set<DOMTopLevelEventType | string> { let listeningSet = elementListeningSets.get(element); if (listeningSet === undefined) { listeningSet = new Set(); elementListeningSets.set(element, listeningSet); } return listeningSet; } 事件合成首先来看看handleTopLevel的逻辑,handleTopLevel主要是缓存祖先元素,避免事件触发后找不到祖先元素报错,接下来就进入runExtractedPluginEventsInBatch方法。
// packages\react-dom\src\events\ReactDOMEventListener.js line 151 function handleTopLevel(bookKeeping: BookKeepingInstance) { let targetInst = bookKeeping.targetInst; // Loop through the hierarchy, in case there's any nested components. // It's important that we build the array of ancestors before calling any // event handlers, because event handlers can modify the DOM, leading to // inconsistencies with ReactMount's node cache. See #1105. let ancestor = targetInst; do { if (!ancestor) { const ancestors = bookKeeping.ancestors; ((ancestors: any): Array<Fiber | null>).push(ancestor); break; } const root = findRootContainerNode(ancestor); if (!root) { break; } const tag = ancestor.tag; if (tag === HostComponent || tag === HostText) { bookKeeping.ancestors.push(ancestor); } ancestor = getClosestInstanceFromNode(root); } while (ancestor); for (let i = 0; i < bookKeeping.ancestors.length; i++) { targetInst = bookKeeping.ancestors[i]; const eventTarget = getEventTarget(bookKeeping.nativeEvent); const topLevelType = ((bookKeeping.topLevelType: any): DOMTopLevelEventType); const nativeEvent = ((bookKeeping.nativeEvent: any): AnyNativeEvent); runExtractedPluginEventsInBatch( topLevelType, targetInst, nativeEvent, eventTarget, bookKeeping.eventSystemFlags, ); } }