如果事件名合法而且是一个函数的时候,就会调用ensureListeningTo()方法注册事件。ensureListeningTo会判断rootContainerElement是否为document或是Fragment,如果是则直接传递给listenTo,如果不是则通过ownerDocument来获取其根节点,对于ownerDocument属性,定义是这样的,ownerDocument可返回某元素的根元素,在HTML中HTML文档本身是元素的根元素,所以可以说明其实大部分的事件都是注册在document上面的,之后便是调用listenTo方法实际注册。
// packages\react-dom\src\client\ReactDOMComponent.js line 272 function ensureListeningTo( rootContainerElement: Element | Node, registrationName: string, ): void { const isDocumentOrFragment = rootContainerElement.nodeType === DOCUMENT_NODE || rootContainerElement.nodeType === DOCUMENT_FRAGMENT_NODE; const doc = isDocumentOrFragment ? rootContainerElement : rootContainerElement.ownerDocument; listenTo(registrationName, doc); }在listenTo()方法中比较重要的就是registrationNameDependencies的概念,对于不同的事件,React会同时绑定多个事件来达到统一的效果。此外listenTo()方法还默认将事件通过trapBubbledEvent绑定,将onBlur、onFocus、onScroll等事件通过trapCapturedEvent绑定,因为这些事件没有冒泡行为,invalid、submit、reset事件以及媒体等事件绑定到当前DOM上。
// packages\react-dom\src\events\ReactBrowserEventEmitter.js line 128 export function listenTo( registrationName: string, // 事件的名称,即为上面的propKey(如onClick) mountAt: Document | Element | Node, // 事件注册的目标容器 ): void { // 获取目标容器已经挂载的事件列表对象,如果没有则初始化为空对象 const listeningSet = getListeningSetForElement(mountAt); // 获取对应事件的依赖事件,比如onChange会依赖TOP_INPUT、TOP_FOCUS等一系列事件 const dependencies = registrationNameDependencies[registrationName]; // 遍历所有的依赖,并挨个进行绑定 for (let i = 0; i < dependencies.length; i++) { const dependency = dependencies[i]; listenToTopLevel(dependency, mountAt, listeningSet); } } export function listenToTopLevel( topLevelType: DOMTopLevelEventType, mountAt: Document | Element | Node, listeningSet: Set<DOMTopLevelEventType | string>, ): void { if (!listeningSet.has(topLevelType)) { // 针对不同的事件来判断使用事件捕获还是事件冒泡 switch (topLevelType) { case TOP_SCROLL: trapCapturedEvent(TOP_SCROLL, mountAt); break; case TOP_FOCUS: case TOP_BLUR: trapCapturedEvent(TOP_FOCUS, mountAt); trapCapturedEvent(TOP_BLUR, mountAt); // We set the flag for a single dependency later in this function, // but this ensures we mark both as attached rather than just one. listeningSet.add(TOP_BLUR); listeningSet.add(TOP_FOCUS); break; case TOP_CANCEL: case TOP_CLOSE: // getRawEventName会返回真实的事件名称,比如onChange => onchange if (isEventSupported(getRawEventName(topLevelType))) { trapCapturedEvent(topLevelType, mountAt); } break; case TOP_INVALID: case TOP_SUBMIT: case TOP_RESET: // We listen to them on the target DOM elements. // Some of them bubble so we don't want them to fire twice. break; default: // 默认将除了媒体事件之外的所有事件都注册冒泡事件 // 因为媒体事件不会冒泡,所以注册冒泡事件毫无意义 const isMediaEvent = mediaEventTypes.indexOf(topLevelType) !== -1; if (!isMediaEvent) { trapBubbledEvent(topLevelType, mountAt); } break; } // 表示目标容器已经注册了该事件 listeningSet.add(topLevelType); } }之后就是熟知的对事件的绑定,以事件冒泡trapBubbledEvent()为例来描述处理流程,可以看到其调用了trapEventForPluginEventSystem方法。
// packages\react-dom\src\events\ReactDOMEventListener.js line 203 export function trapBubbledEvent( topLevelType: DOMTopLevelEventType, element: Document | Element | Node, ): void { trapEventForPluginEventSystem(element, topLevelType, false); }可以看到React将事件分成了三类,优先级由低到高:
DiscreteEvent离散事件,例如blur、focus、 click、 submit、 touchStart,这些事件都是离散触发的。
UserBlockingEvent用户阻塞事件,例如touchMove、mouseMove、scroll、drag、dragOver等等,这些事件会阻塞用户的交互。