/**
* Summary of `ReactBrowserEventEmitter` event handling:
*
* - Top-level delegation is used to trap most native browser events. This
* may only occur in the main thread and is the responsibility of
* ReactDOMEventListener, which is injected and can therefore support
* pluggable event sources. This is the only work that occurs in the main
* thread.
*
* - We normalize and de-duplicate events to account for browser quirks. This
* may be done in the worker thread.
*
* - Forward these native events (with the associated top-level type used to
* trap it) to `EventPluginHub`, which in turn will ask plugins if they want
* to extract any synthetic events.
*
* - The `EventPluginHub` will then process each event by annotating them with
* "dispatches", a sequence of listeners and IDs that care about that event.
*
* - The `EventPluginHub` then dispatches the events.
*/
/**
* React和事件系统概述:
*
* +------------+ .
* | DOM | .
* +------------+ .
* | .
* v .
* +------------+ .
* | ReactEvent | .
* | Listener | .
* +------------+ . +-----------+
* | . +--------+|SimpleEvent|
* | . | |Plugin |
* +-----|------+ . v +-----------+
* | | | . +--------------+ +------------+
* | +-----------.--->|EventPluginHub| | Event |
* | | . | | +-----------+ | Propagators|
* | ReactEvent | . | | |TapEvent | |------------|
* | Emitter | . | |<---+|Plugin | |other plugin|
* | | . | | +-----------+ | utilities |
* | +-----------.--->| | +------------+
* | | | . +--------------+
* +-----|------+ . ^ +-----------+
* | . | |Enter/Leave|
* + . +-------+|Plugin |
* +-------------+ . +-----------+
* | application | .
* |-------------| .
* | | .
* | | .
* +-------------+ .
* .
*/
// packages\react-dom\src\client\ReactDOMComponent.js line 308functionsetInitialDOMProperties(tag: string,domElement: Element,rootContainerElement: Element | Document,nextProps: Object,isCustomComponentTag: boolean,):void{for(const propKey in nextProps){if(!nextProps.hasOwnProperty(propKey)){continue;}const nextProp = nextProps[propKey];if(propKey ===STYLE){if(__DEV__){if(nextProp){// Freeze the next style object so that we can assume it won't be// mutated. We have already warned for this in the past. Object.freeze(nextProp);}}// Relies on `updateStylesByID` not mutating `styleUpdates`.setValueForStyles(domElement, nextProp);}elseif(/* ... */){// ...}elseif(registrationNameModules.hasOwnProperty(propKey)){// 对事件名进行合法性检验,只有合法的事件名才会被识别并进行事件绑定if(nextProp !=null){if(__DEV__ &&typeof nextProp !=='function'){warnForInvalidEventListener(propKey, nextProp);}ensureListeningTo(rootContainerElement, propKey);// 开始注册事件}}elseif(nextProp !=null){setValueForProperty(domElement, propKey, nextProp, isCustomComponentTag);}}}
// packages\react-dom\src\events\ReactBrowserEventEmitter.js line 128exportfunctionlistenTo(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);}}exportfunctionlistenToTopLevel(topLevelType: DOMTopLevelEventType,mountAt: Document | Element | Node,listeningSet: Set<DOMTopLevelEventType | string>,):void{if(!listeningSet.has(topLevelType)){// 针对不同的事件来判断使用事件捕获还是事件冒泡switch(topLevelType){caseTOP_SCROLL:trapCapturedEvent(TOP_SCROLL, mountAt);break;caseTOP_FOCUS:caseTOP_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;caseTOP_CANCEL:caseTOP_CLOSE:// getRawEventName会返回真实的事件名称,比如onChange => onchangeif(isEventSupported(getRawEventName(topLevelType))){trapCapturedEvent(topLevelType, mountAt);}break;caseTOP_INVALID:caseTOP_SUBMIT:caseTOP_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);}}
// packages\react-dom\src\events\ReactDOMEventListener.js line 151functionhandleTopLevel(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,);}}
// packages\legacy-events\EventPluginHub.js line 133functionextractPluginEvents(topLevelType: TopLevelType,targetInst:null| Fiber,nativeEvent: AnyNativeEvent,nativeEventTarget: EventTarget,eventSystemFlags: EventSystemFlags,): Array<ReactSyntheticEvent>| ReactSyntheticEvent |null{let events =null;for(let i =0; i < plugins.length; i++){// Not every plugin in the ordering may be loaded at runtime.constpossiblePlugin: PluginModule<AnyNativeEvent>= plugins[i];if(possiblePlugin){const extractedEvents = possiblePlugin.extractEvents( topLevelType, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags,);if(extractedEvents){ events =accumulateInto(events, extractedEvents);}}}return events;}
// packages\legacy-events\EventBatching.js line 42exportfunctionrunEventsInBatch(events: Array<ReactSyntheticEvent>| ReactSyntheticEvent |null,){if(events !==null){ eventQueue =accumulateInto(eventQueue, events);}// Set `eventQueue` to null before processing it so that we can tell if more// events get enqueued while processing.const processingEventQueue = eventQueue; eventQueue =null;if(!processingEventQueue){return;}forEachAccumulated(processingEventQueue, executeDispatchesAndReleaseTopLevel);invariant(!eventQueue,'processEventQueue(): Additional events were enqueued while processing '+'an event queue. Support for this has not yet been implemented.',);// This would be a good time to rethrow if any of the event handlers threw.rethrowCaughtError();}// packages\legacy-events\EventPluginUtils.js line 76exportfunctionexecuteDispatchesInOrder(event){const dispatchListeners = event._dispatchListeners;const dispatchInstances = event._dispatchInstances;if(__DEV__){validateEventDispatches(event);}if(Array.isArray(dispatchListeners)){for(let i =0; i < dispatchListeners.length; i++){if(event.isPropagationStopped()){break;}// Listeners and Instances are two parallel arrays that are always in sync.executeDispatch(event, dispatchListeners[i], dispatchInstances[i]);}}elseif(dispatchListeners){executeDispatch(event, dispatchListeners, dispatchInstances);} event._dispatchListeners =null; event._dispatchInstances =null;}// packages\legacy-events\EventPluginUtils.js line 66exportfunctionexecuteDispatch(event, listener, inst){const type = event.type ||'unknown-event'; event.currentTarget =getNodeFromInstance(inst);invokeGuardedCallbackAndCatchFirstError(type, listener,undefined, event); event.currentTarget =null;}// packages\shared\ReactErrorUtils.js line 67exportfunction invokeGuardedCallbackAndCatchFirstError<A,B,C,D,E,F, Context,>(name: string |null,func:(a:A,b:B,c:C,d:D,e:E,f:F)=>void,context: Context,a:A,b:B,c:C,d:D,e:E,f:F,):void{invokeGuardedCallback.apply(this, arguments);if(hasError){const error =clearCaughtError();if(!hasRethrowError){ hasRethrowError =true; rethrowError = error;}}}