React
本身實現了一套高效的事件註冊、存儲、分發和重用邏輯,在DOM
事件體系基礎上作了很大改進,減小了內存消耗,簡化了事件邏輯,並最大程度地解決了IE
等瀏覽器的不兼容問題。javascript
React
的合成事件SyntheticEvent
實際上就是React
本身在內部實現的一套事件處理機制,它是瀏覽器的原生事件的跨瀏覽器包裝器,除兼容全部瀏覽器外,它還擁有和瀏覽器原生事件相同的接口,包括stopPropagation()
和preventDefault()
,合成事件與瀏覽器的原生事件不一樣,也不會直接映射到原生事件,也就是說一般不要使用addEventListener
爲已建立的DOM
元素添加監聽器,而應該直接使用React
中定義的事件機制,並且在混用的狀況下原生事件若是定義了阻止冒泡可能會阻止合成事件的執行,固然若是確實須要使用原生事件去處理需求,能夠經過事件觸發傳遞的SyntheticEvent
對象的nativeEvent
屬性得到原生Event
對象的引用,React
中的事件有如下幾個特色:html
React
上註冊的事件最終會綁定在document
這個DOM
上,而不是React
組件對應的DOM
,經過這種方式減小內存開銷,全部的事件都綁定在document
上,其餘節點沒有綁定事件,實際上就是事件委託的。React
自身實現了一套事件冒泡機制,使用React
實現的Event
對象與原生Event
對象不一樣,不能相互混用。React
經過隊列的形式,從觸發的組件向父組件回溯,而後調用他們JSX
中定義的callback
。React
的合成事件SyntheticEvent
與瀏覽器的原生事件不一樣,也不會直接映射到原生事件。React
經過對象池的形式管理合成事件對象的建立和銷燬,減小了垃圾的生成和新對象內存的分配,提升了性能。對於每一個SyntheticEvent
對象都包含如下屬性:java
boolean bubbles boolean cancelable DOMEventTarget currentTarget boolean defaultPrevented number eventPhase boolean isTrusted DOMEvent nativeEvent void preventDefault() boolean isDefaultPrevented() void stopPropagation() boolean isPropagationStopped() void persist() DOMEventTarget target number timeStamp string type
支持的合成事件一覽,注意如下的事件處理函數在冒泡階段被觸發,如需註冊捕獲階段的事件處理函數,則應爲事件名添加Capture
,例如處理捕獲階段的點擊事件請使用onClickCapture
,而不是onClick
。node
<!-- 剪貼板事件 --> onCopy onCut onPaste <!-- 複合事件 --> onCompositionEnd onCompositionStart onCompositionUpdate <!-- 鍵盤事件 --> onKeyDown onKeyPress onKeyUp <!-- 焦點事件 --> onFocus onBlur <!-- 表單事件 --> onChange onInput onInvalid onReset onSubmit <!-- 通用事件 --> onError onLoad <!-- 鼠標事件 --> onClick onContextMenu onDoubleClick onDrag onDragEnd onDragEnter onDragExit onDragLeave onDragOver onDragStart onDrop onMouseDown onMouseEnter onMouseLeave onMouseMove onMouseOut onMouseOver onMouseUp <!-- 指針事件 --> onPointerDown onPointerMove onPointerUp onPointerCancel onGotPointerCapture onLostPointerCapture onPointerEnter onPointerLeave onPointerOver onPointerOut <!-- 選擇事件 --> onSelect <!-- 觸摸事件 --> onTouchCancel onTouchEnd onTouchMove onTouchStart <!-- UI 事件 --> onScroll <!-- 滾輪事件 --> onWheel <!-- 媒體事件 --> onAbort onCanPlay onCanPlayThrough onDurationChange onEmptied onEncrypted onEnded onError onLoadedData onLoadedMetadata onLoadStart onPause onPlay onPlaying onProgress onRateChange onSeeked onSeeking onStalled onSuspend onTimeUpdate onVolumeChange onWaiting <!-- 圖像事件 --> onLoad onError <!-- 動畫事件 --> onAnimationStart onAnimationEnd onAnimationIteration <!-- 過渡事件 --> onTransitionEnd <!-- 其餘事件 --> onToggle <!-- https://zh-hans.reactjs.org/docs/events.html -->
一個簡單的示例,同時綁定在一個DOM
上的原生事件與React
事件,由於原生事件阻止冒泡而致使React
事件沒法執行,同時咱們也能夠看到React
傳遞的event
並非原生Event
對象的實例,而是React
自行實現維護的一個event
對象。react
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>React</title> </head> <body> <div id="root"></div> </body> <script src="https://unpkg.zhimg.com/react@17/umd/react.development.js"></script> <script src="https://unpkg.zhimg.com/react-dom@17/umd/react-dom.development.js"></script> <script src="https://unpkg.zhimg.com/@babel/standalone/babel.min.js"></script> <script type="text/babel"> class ReactEvent extends React.PureComponent { componentDidMount(){ document.getElementById("btn-reactandnative").addEventListener("click", (e) => { console.log("原生事件執行", "handleNativeAndReact"); console.log("event instanceof Event:", e instanceof Event); e.stopPropagation(); // 阻止冒泡即會影響了React的事件執行 }); } handleNativeAndReact = (e) => { console.log("React事件執行", "handleNativeAndReact"); console.log("event instanceof Event:", e instanceof Event); } handleClick = (e) => { console.log("React事件執行", "handleClick"); console.log("event instanceof Event:", e instanceof Event); } render() { return ( <div className="pageIndex"> <button id="btn-confirm" onClick={this.handleClick}>React 事件</button> <button id="btn-reactandnative" onClick={this.handleNativeAndReact}>原生 + React 事件</button> </div> ) } } var vm = ReactDOM.render( <> <ReactEvent /> </>, document.getElementById("root") ); </script> </html>
簡單來講,在掛載的時候,經過listenerBank
把事件存起來了,觸發的時候document
進行dispatchEvent
,找到觸發事件的最深的一個節點,向上遍歷拿到全部的callback
放在eventQueue
,根據事件類型構建event
對象,遍歷執行eventQueue
,不簡單點說,咱們能夠查看一下React
對於事件處理的源碼實現,commit id
爲4ab6305
,TAG
是React16.10.2
,在React17
再也不往document
上掛事件委託,而是掛到DOM
容器上,目錄結構都有了很大更改,咱們仍是依照React16
,首先來看一下事件的處理流程。git
/** * 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\events\ReactBrowserEventEmitter.js
中就描述了上邊的流程,而且還有相應的英文註釋,使用google
翻譯一下,這個太概述了,因此仍是須要詳細描述一下,在事件處理以前,咱們編寫的JSX
須要通過babel
的編譯,建立虛擬DOM
,並處理組件props
,拿到事件類型和回調fn
等,以後即是事件註冊、存儲、合成、分發、執行階段。github
Top-level delegation
用於捕獲最原始的瀏覽器事件,它主要由ReactEventListener
負責,ReactEventListener
被注入後能夠支持插件化的事件源,這一過程發生在主線程。React
對事件進行規範化和重複數據刪除,以解決瀏覽器的問題,這能夠在工做線程中完成。EventPluginHub
,後者將詢問插件是否要提取任何合成事件。EventPluginHub
將經過爲每一個事件添加dispatches
(引用該事件的偵聽器和ID
的序列)來對其進行註釋來進行處理。EventPluginHub
會調度分派事件。首先會調用setInitialDOMProperties()
判斷是否在registrationNameModules
列表中,在的話便註冊事件,列表包含了能夠註冊的事件。segmentfault
// packages\react-dom\src\client\ReactDOMComponent.js line 308 function setInitialDOMProperties( 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); }else if(/* ... */){ // ... } else if (registrationNameModules.hasOwnProperty(propKey)) { // 對事件名進行合法性檢驗,只有合法的事件名纔會被識別並進行事件綁定 if (nextProp != null) { if (__DEV__ && typeof nextProp !== 'function') { warnForInvalidEventListener(propKey, nextProp); } ensureListeningTo(rootContainerElement, propKey); // 開始註冊事件 } } else if (nextProp != null) { setValueForProperty(domElement, propKey, nextProp, isCustomComponentTag); } } }
若是事件名合法並且是一個函數的時候,就會調用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
等等,這些事件會阻塞用戶的交互。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, ); } }
在runExtractedPluginEventsInBatch
中extractPluginEvents
用於經過不一樣的插件合成事件events
,而runEventsInBatch
則是完成事件的觸發。
// packages\legacy-events\EventPluginHub.js line 160 export function runExtractedPluginEventsInBatch( topLevelType: TopLevelType, targetInst: null | Fiber, nativeEvent: AnyNativeEvent, nativeEventTarget: EventTarget, eventSystemFlags: EventSystemFlags, ) { const events = extractPluginEvents( topLevelType, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags, ); runEventsInBatch(events); }
在extractPluginEvents
中遍歷全部插件的extractEvents
方法合成事件,若是這個插件適合於這個events
則返回它,不然返回null
。默認的有5
種插件SimpleEventPlugin
、EnterLeaveEventPlugin
、ChangeEventPlugin
、SelectEventPlugin
、BeforeInputEventPlugin
。
// packages\legacy-events\EventPluginHub.js line 133 function extractPluginEvents( 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. const possiblePlugin: PluginModule<AnyNativeEvent> = plugins[i]; if (possiblePlugin) { const extractedEvents = possiblePlugin.extractEvents( topLevelType, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags, ); if (extractedEvents) { events = accumulateInto(events, extractedEvents); } } } return events; }
不一樣的事件類型會有不一樣的合成事件基類,而後再經過EventConstructor.getPooled
生成事件,accumulateTwoPhaseDispatches
用於獲取事件回調函數,最終調的是getListener
方法。
爲了不頻繁建立和釋放事件對象致使性能損耗(對象建立和垃圾回收),React
使用一個事件池來負責管理事件對象(在React17
中再也不使用事件池機制),使用完的事件對象會放回池中,以備後續的複用,也就意味着事件處理器同步執行完後,SyntheticEvent
屬性就會立刻被回收,不能訪問了,也就是事件中的e
不能用了,若是要用的話,能夠經過一下兩種方式:
e.persist()
,告訴React
不要回收對象池,在React17
依舊能夠調用只是沒有實際做用。e. nativeEvent
,由於它是持久引用的。事件分發就是遍歷找到當前元素及父元素全部綁定的事件,將全部的事件放到event._dispachListeners
隊列中,以備後續的執行。
// packages\legacy-events\EventPropagators.js line 47 function accumulateDirectionalDispatches(inst, phase, event) { if (__DEV__) { warningWithoutStack(inst, 'Dispatching inst must not be null'); } const listener = listenerAtPhase(inst, event, phase); if (listener) { // 將提取到的綁定添加到_dispatchListeners中 event._dispatchListeners = accumulateInto( event._dispatchListeners, listener, ); event._dispatchInstances = accumulateInto(event._dispatchInstances, inst); } }
執行事件隊列用到的方法是runEventsInBatch
,遍歷執行executeDispatchesInOrder
方法,經過executeDispatch
執行調度,最終執行回調函數是經過invokeGuardedCallbackAndCatchFirstError
方法。
// packages\legacy-events\EventBatching.js line 42 export function runEventsInBatch( 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 76 export function executeDispatchesInOrder(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]); } } else if (dispatchListeners) { executeDispatch(event, dispatchListeners, dispatchInstances); } event._dispatchListeners = null; event._dispatchInstances = null; } // packages\legacy-events\EventPluginUtils.js line 66 export function executeDispatch(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 67 export function 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; } } }
https://github.com/WindrunnerMax/EveryDay
https://zhuanlan.zhihu.com/p/53961511 https://zhuanlan.zhihu.com/p/25883536 https://zhuanlan.zhihu.com/p/140791931 https://www.jianshu.com/p/8d8f9aa4b033 https://toutiao.io/posts/28of14w/preview https://juejin.cn/post/6844903988794671117 https://segmentfault.com/a/1190000015142568 https://zh-hans.reactjs.org/docs/events.html https://github.com/UNDERCOVERj/tech-blog/issues/13 https://blog.csdn.net/kyooo0/article/details/111829693