某次被問到 React
事件機制的問題,關於這一塊我確實不怎麼清楚,由於平時大部分工做都是用 Vue
,對於 React
的熟悉程度只限於會用,具體實現邏輯還真沒專門學習過,可是總不能就說本身不清楚吧,好在我瞭解 Vue
的事件機制,因而就把 Vue
的事件機制說了一遍,最後再來一句「我以爲 React
應該和 Vue
的差很少」node
後來我想了下應該沒那麼簡單,因而網上搜了下相關文章,發現果真是被我想得太簡單了,Vue
經過編譯模板,解析出事件指令,將事件和事件回調附加到 vnode tree
上,在 patch
過程當中的建立階段和更新階段都會對這個 vnode tree
進行處理,拿到每一個 vnode
上附加的事件信息,就能夠調用原生 DOM API
對相應事件進行註冊或移除,流程仍是比較清晰的,而React
則是單獨實現了一套事件機制react
本文以
React v16.5.2
爲基礎進行源碼分析數組
在 react
源碼的 react-dom/src/events/ReactBrowserEventEmitter.js
文件的開頭,有這麼一大段註釋:瀏覽器
/** * Summary of `ReactBrowserEventEmitter` event handling: * * - Top-level delegation is used to ...... * ...... * * +------------+ . * | DOM | . * +------------+ . * | . * v . * +------------+ . * | ReactEvent | . * | Listener | . * +------------+ . +-----------+ * | . +--------+|SimpleEvent| * | . | |Plugin | * +-----|------+ . v +-----------+ * | | | . +--------------+ +------------+ * | +-----------.--->|EventPluginHub| | Event | * | | . | | +-----------+ | Propagators| * | ReactEvent | . | | |TapEvent | |------------| * | Emitter | . | |<---+|Plugin | |other plugin| * | | . | | +-----------+ | utilities | * | +-----------.--->| | +------------+ * | | | . +--------------+ * +-----|------+ . ^ +-----------+ * | . | |Enter/Leave| * + . +-------+|Plugin | * +-------------+ . +-----------+ * | application | . * |-------------| . * | | . * | | . * +-------------+ . * . * React Core . General Purpose Event Plugin System */
複製代碼
這段註釋第一段文本內容被我省略掉了,其主要是在大概描述 React
的事件機制,也就是這個文件中的代碼要作的一些事情,大概意思就是說事件委託是很經常使用的一種瀏覽器事件優化策略,因而 React
就接管了這件事情,而且還貼心地消除了瀏覽器間的差別,賦予開發者跨瀏覽器的開發體驗,主要是使用 EventPluginHub
這個東西來負責調度事件的存儲,合成事件並以對象池的方式實現建立和銷燬,至於下面的結構圖形,則是對事件機制的一個圖形化描述緩存
根據這段註釋,大概能夠提煉出如下幾點內容:app
React
事件使用了事件委託的機制,通常事件委託的做用都是爲了減小頁面的註冊事件數量,減小內存開銷,優化瀏覽器性能,React
這麼作也是有這麼一個目的,除此以外,也是爲了可以更好的管理事件,實際上,React
中全部的事件最後都是被委託到了 document
這個頂級DOM
上document
上,那麼確定有一套管理機制,全部的事件都是以一種先進先出的隊列方式進行觸發與回調React
中就存在了本身的 合成事件(SyntheticEvent
),合成事件由對應的 EventPlugin
負責合成,不一樣類型的事件由不一樣的 plugin
合成,例如 SimpleEvent Plugin
、TapEvent Plugin
等EventPluginHub
這個東西來負責合成事件對象的建立和銷燬下文均如下述這段代碼爲示例進行分析:dom
export default class MyBox extends React.Component {
clickHandler(e) {
console.log('click callback', e)
}
render() {
return (
<div className="box" onClick={this.clickHandler}>文本內容</div>
)
}
}
複製代碼
只看相關主體流程,其餘諸如 vnode
的建立等前提流程就無論了,從setInitialDOMProperties
這個方法開始看起,這個方法主要用於遍歷 ReactNode
的 props
對象,給最後將要真正渲染的真實 DOM
對象設置一系列的屬性,例如 style
、class
、autoFocus
,也包括innerHTML
、event
的處理等,示例中 .box
元素的 props
對象結構以下:函數
這個方法中有個 case
,就是專門用於處理事件的:oop
// react-dom/src/client/ReactDOMComponent.js
else if (registrationNameModules.hasOwnProperty(propKey)) {
if (nextProp != null) {
if (true && typeof nextProp !== 'function') {
warnForInvalidEventListener(propKey, nextProp);
}
// 處理事件類型的 props
ensureListeningTo(rootContainerElement, propKey);
}
}
複製代碼
其中的 registrationNameModules
這個變量,裏面存在一大堆的屬性,都是與 React
的事件相關:源碼分析
例子中的 onClick
這個 props
顯然符合,因此能夠執行 ensureListeningTo
這個方法:
// react-dom/src/client/ReactDOMComponent.js
function ensureListeningTo(rootContainerElement, registrationName) {
var isDocumentOrFragment = rootContainerElement.nodeType === DOCUMENT_NODE || rootContainerElement.nodeType === DOCUMENT_FRAGMENT_NODE;
var doc = isDocumentOrFragment ? rootContainerElement : rootContainerElement.ownerDocument;
listenTo(registrationName, doc);
}
複製代碼
這個方法中,首先判斷了 rootContainerElement
是否是一個 document
或者 Fragment
(文檔片斷節點),示例中傳過來的是 .box
這個 div
,顯然不是,因此 doc
這個變量就被賦值爲 rootContainerElement.ownerDocument
,這個東西其實就是 .box
所在的 document
元素,把這個document
傳到下面的 listenTo
裏了,事件委託也就是在這裏作的,全部的事件最終都會被委託到 document
或者 fragment
上去,大部分狀況下都是 document
,而後這個 registrationName
就是事件名稱 onClick
接着開始執行 listenTo
方法,這個方法其實就是註冊事件的入口了,方法裏面有這麼一句:
// react-dom/src/events/ReactBrowserEventEmitter.js
var dependencies = registrationNameDependencies[registrationName];
複製代碼
registrationName
就是傳過來的 onClick
,而變量 registrationNameDependencies
是一個存儲了 React
事件名與瀏覽器原生事件名對應的一個 Map
,能夠經過這個 map
拿到相應的瀏覽器原生事件名,registrationNameDependencies
結構以下:
能夠看到,React
是給事件名作了一些跨瀏覽器兼容事情的,好比傳入 onChange
事件,會自動對應上 blur change click focus
等多種瀏覽器原生事件
接下來,遍歷這個 dependencies
數組,進入到如下 case
:
// react-dom/src/events/ReactBrowserEventEmitter.js
switch (dependency) {
// 省略一些代碼
default:
// By default, listen on the top level to all non-media events.
// Media events don't bubble so adding the listener wouldn't do anything.
var isMediaEvent = mediaEventTypes.indexOf(dependency) !== -1;
if (!isMediaEvent) {
trapBubbledEvent(dependency, mountAt);
}
break;
}
複製代碼
除了 scroll focus blur cancel close
方法走 trapCapturedEvent
方法,invalid submit reset
方法不處理以外,剩下的事件類型全走default
,執行 trapBubbledEvent
這個方法,trapCapturedEvent
和 trapBubbledEvent
兩者惟一的不一樣之處就在於,對於最終的合成事件,前者註冊捕獲階段的事件監聽器,然後者則註冊冒泡階段的事件監聽器
因爲大部分合成事件的代理註冊的都是冒泡階段的事件監聽器,也就是委託到
document
上註冊的是冒泡階段的事件監聽器,因此就算你顯示聲明瞭一個捕獲階段的React
事件,例如onClickCapture
,此事件的響應也會晚於原生事件的捕獲事件以及冒泡事件 實際上,全部原生事件的響應(不管是冒泡事件仍是捕獲事件),都將早於React
合成事件(SyntheticEvent
),對原生事件調用e.stopPropagation()
將阻止對應SyntheticEvent
的響應,由於對應的事件根本沒法到達document
這個事件委託層就被阻止掉了
兩者區別不大,trapBubbledEvent
用的最多,本示例也將執行這個方法,因此就跟着這個方法看下去:
// react-dom/src/events/EventListener.js
// 對於本示例來講,topLevelType就是 click,element就是 document
function trapBubbledEvent(topLevelType, element) {
if (!element) {
return null;
}
var dispatch = isInteractiveTopLevelEventType(topLevelType) ? dispatchInteractiveEvent : dispatchEvent;
addEventBubbleListener(element, getRawEventName(topLevelType),
// Check if interactive and wrap in interactiveUpdates
dispatch.bind(null, topLevelType));
}
複製代碼
addEventBubbleListener
這個方法接收三個參數,在本示例中,第一個參數 element
其實就是 document
元素,getRawEventName(topLevelType)
就是 click
事件,第三個參數的 dispatch
就是 dispatchInteractiveEvent
,dispatchInteractiveEvent
其實最後仍是會執行 dispatchEvent
這個方法,只是在執行這個方法以前作了一些額外的事情,這裏不須要關心,能夠暫且認爲兩者是同樣的
看下 addEventBubbleListener
這個方法:
// react-dom/src/events/EventListener.js
export function addEventBubbleListener( element: Document | Element, eventType: string, listener: Function, ): void {
element.addEventListener(eventType, listener, false);
}
複製代碼
這個方法很簡單,就是用 addEventListener
給 document
註冊了一個冒泡事件,listener
這個事件的回調就是以前傳入 dispatch.bind(null, topLevelType)
流程圖以下:
既然全部的事件都委託註冊到了 document
上,那麼事件觸發的時候,確定須要一個事件分發的過程,來找到究竟是哪一個元素觸發的事件,並執行相應的回調函數,須要注意的是,因爲元素自己並無註冊任何事件,而是委託到了 document
上,因此這個將被觸發的事件是 React
自帶的合成事件,而非瀏覽器原生事件,但總之都是須要一個分發的過程的
在前面的 事件註冊 中已經提到過,註冊到 document
上的事件,對應的回調函數都會觸發 dispatchEvent
這個方法,進入這個方法:
// react-dom/src/events/ReactDOMEventListener.js
const nativeEventTarget = getEventTarget(nativeEvent);
let targetInst = getClosestInstanceFromNode(nativeEventTarget);
複製代碼
首先找到事件觸發的 DOM
和 React Component
,找真實 DOM
比較好找,直接取事件回調的 event
參數的 target | srcElement | window
便可,而後這個 nativeEventTarget
對象上掛在了一個以 __reactInternalInstance
開頭的屬性,這個屬性就是 internalInstanceKey
,其值就是當前 React
實例對應的 React Component
而後繼續往下看:
try {
// Event queue being processed in the same cycle allows
// `preventDefault`.
batchedUpdates(handleTopLevel, bookKeeping);
} finally {
releaseTopLevelCallbackBookKeeping(bookKeeping);
}
複製代碼
batchedUpdates
,字面意思就是批處理更新,這裏實際上就是把當前觸發的事件放入了批處理隊列中,其中,handleTopLevel
是事件分發的核心所在:
// react-dom/src/events/ReactDOMEventListener.js
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) {
bookKeeping.ancestors.push(ancestor);
break;
}
const root = findRootContainerNode(ancestor);
if (!root) {
break;
}
bookKeeping.ancestors.push(ancestor);
ancestor = getClosestInstanceFromNode(root);
} while (ancestor);
複製代碼
首先在事件回調以前,根據當前組件,向上遍歷獲得其全部的父組件,存儲到 ancestors
中,因爲全部的事件都委託到了 document
上,因此在事件觸發後,不管是冒泡事件仍是捕獲事件,其在相關元素上的觸發確定是要有一個次序關係的,好比在子元素和父元素上都註冊了一個鼠標點擊冒泡事件,事件觸發後,確定是子元素的事件響應快於父元素,因此在事件隊列裏,子元素就要排在父元素前面,而在事件回調以前就要進行緩存,緣由在代碼的註釋裏也已經解釋得很清楚了,大概意思就是事件回調可能會改變 DOM
結構,因此要先遍歷好組件層級關係,緩存起來
繼續往下:
// react-dom/src/events/ReactDOMEventListener.js
for (let i = 0; i < bookKeeping.ancestors.length; i++) {
targetInst = bookKeeping.ancestors[i];
runExtractedEventsInBatch(
bookKeeping.topLevelType,
targetInst,
bookKeeping.nativeEvent,
getEventTarget(bookKeeping.nativeEvent),
);
}
複製代碼
使用了一個 for
循環來遍歷這個 React Component
及其全部的父組件,執行 runExtractedEventsInBatch
方法,這裏的遍歷方法是從前日後遍歷,前面說了,咱們這裏分析的是 trapBubbledEvent
,也就是冒泡事件,因此這裏對應到組件層級上就是由子元素到父元素,若是這裏是分析 trapCapturedEvent
,即捕獲事件,那麼這個從前日後的順序就對應父元素到子元素了 提醒一點,不管是 trapBubbledEvent
仍是 trapCapturedEvent
,這裏都是針對 document
元素而不是實際的元素,不要弄混了
至於循環中調用的 runExtractedEventsInBatch
方法,其實就是事件執行的入口了
runExtractedEventsInBatch
這個方法中又調用了兩個方法:extractEvents
、runEventsInBatch
,extractEvents
用於構造合成事件,runEventsInBatch
用於批處理 extractEvents
構造出的合成事件
plugin
先看 extractEvents
// packages/events/EventPluginHub.js
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,
);
if (extractedEvents) {
events = accumulateInto(events, extractedEvents);
}
}
}
複製代碼
首先遍歷 plugins
,這個 plugins
就是全部事件合成 plugins
的集合數組,一共 5
種(v15.x
版本是 7
種),這些 plugins
都位於 react-dom/src/events
這個文件夾下,以單獨文件的形式存在,文件名以 EventPlugin
結尾的就是,它們是在 EventPluginHub
初始化階段注入進去的:
// react-dom/src/client/ReactDOMClientInjection.js
EventPluginHub.injection.injectEventPluginsByName({
SimpleEventPlugin: SimpleEventPlugin,
EnterLeaveEventPlugin: EnterLeaveEventPlugin,
ChangeEventPlugin: ChangeEventPlugin,
SelectEventPlugin: SelectEventPlugin,
BeforeInputEventPlugin: BeforeInputEventPlugin,
});
複製代碼
extractEvents
方法裏用了一個 for
循環,把全部的 plugin
全都執行了一遍,我的理解沒這個必要,找到合適的 plugin
執行完以後就能夠直接 break
掉了 好比對於本示例的 click
事件來講,合適的 plugin
是 SimpleEventPlugin
,其餘的 plugin
就算是進入走了一遍也只是作了個無用功而已,由於執行完其餘 plugin
後獲得的 extractedEvents
都不知足 if (extractedEvents)
這個條件,沒法給 events
這個變量賦值或者覆蓋賦值,固然,也可能這段代碼還有其餘比較隱祕的做用吧
possiblePlugin.extractEvents
這一句就是調用相應 plugin
的構造合成事件的方法,其餘的 plugin
就不展開分析了,針對本示例的 SimpleEventPlugin
,來看下它的 extractEvents
:
// react-dom/src/events/SimpleEventPlugin.js
const dispatchConfig = topLevelEventsToDispatchConfig[topLevelType];
if (!dispatchConfig) {
return null;
}
複製代碼
首先,看下 topLevelEventsToDispatchConfig
這個對象中有沒有 topLevelType
這個屬性,只要有,那麼說明當前事件可使用 SimpleEventPlugin
構造合成事件,對於本示例來講,topLevelType
就是 click
,而topLevelEventsToDispatchConfig
結構以下:
這些屬性就是一些常見的事件名,顯然 click
是 topLevelEventsToDispatchConfig
的一個屬性名,符合條件,能夠繼續往下執行,下面緊跟着的是一個 switch...case
的判斷語句,對於本示例來講,將在下面這個 case
處 break
掉:
// react-dom/src/events/SimpleEventPlugin.js
case TOP_CLICK:
// 省略了一些代碼
EventConstructor = SyntheticMouseEvent;
break;
複製代碼
SyntheticMouseEvent
能夠看作是 SimpleEventPlugin
的一個具體的子 plugin
,至關因而對 SimpleEventPlugin
這個大概念的 plugin
又細分了一層,除了 SyntheticMouseEvent
以外還有 SyntheticWheelEvent
、SyntheticClipboardEvent
、SyntheticTouchEvent
等
設置好具體的 EventConstructor
後,繼續往下執行:
// react-dom/src/events/SimpleEventPlugin.js
const event = EventConstructor.getPooled(
dispatchConfig,
targetInst,
nativeEvent,
nativeEventTarget,
);
accumulateTwoPhaseDispatches(event);
return event;
複製代碼
getPooled
就是從 event
對象池中取出合成事件,這種操做是 React
的一大亮點,將全部的事件緩存在對象池中,能夠大大下降對象建立和銷燬的時間,提高性能
getPooled
是 EventConstructor
上的一個方法,這個方法是在 EventConstructor
初始化的時候掛上去的,但歸根到底,這個方法是位於 SyntheticEvent
這個對象上,流程示意圖以下:
這個 getPooled
其實就是 getPooledEvent
,在 SyntheticEvent
初始化的過程當中就被設置好初始值了:
// packages/events/SyntheticEvent.js
addEventPoolingTo(SyntheticEvent);
// 省略部分代碼
function addEventPoolingTo(EventConstructor) {
EventConstructor.eventPool = [];
EventConstructor.getPooled = getPooledEvent;
EventConstructor.release = releasePooledEvent;
}
複製代碼
那麼看下 getPooledEvent
:
// packages/events/SyntheticEvent.js
function getPooledEvent(dispatchConfig, targetInst, nativeEvent, nativeInst) {
const EventConstructor = this;
if (EventConstructor.eventPool.length) {
const instance = EventConstructor.eventPool.pop();
EventConstructor.call(
instance,
dispatchConfig,
targetInst,
nativeEvent,
nativeInst,
);
return instance;
}
return new EventConstructor(
dispatchConfig,
targetInst,
nativeEvent,
nativeInst,
);
}
複製代碼
首次觸發事件的時候(在本示例中就是 click
事件),EventConstructor.eventPool.length
爲 0
,由於這個時候是第一次事件觸發,對象池中沒有對應的合成事件引用,因此須要初始化,後續再觸發事件的時候,就無需 new
了,而是走上面那個邏輯,直接從對象池中取,經過 EventConstructor.eventPool.pop();
獲取合成對象實例
這裏先看下初始化的流程,會執行 new EventConstructor
這一句,前面說了,這個東西能夠看作是 SyntheticEvent
的子類,或者是由 SyntheticEvent
擴展而來的東西,怎麼擴展的呢,其實是使用了一個 extend
方法:
const SyntheticMouseEvent = SyntheticUIEvent.extend({
screenX: null,
screenY: null,
clientX: null,
clientY: null,
pageX: null,
pageY: null,
// 省略部分代碼
})
複製代碼
首先,SyntheticMouseEvent
這個合成事件,有本身的一些屬性,這些屬性其實和瀏覽器原生的事件回調參數對象 event
的屬性沒多大差異,都有對於當前事件的一些描述,甚至連屬性名都同樣,只不過相比於瀏覽器原生的事件回調參數對象 event
來講,SyntheticMouseEvent
或者說 合成事件SyntheticEvent
的屬性是由 React
主動生成,通過 React
的內部處理,使得其上附加的描述屬性徹底符合 W3C
的標準,所以在事件層面上具備跨瀏覽器兼容性,與原生的瀏覽器事件同樣擁有一樣的接口,也具有stopPropagation()
和 preventDefault()
等方法
對於本示例中的點擊事件回調方法來講:
clickHandler(e) {
console.log('click callback', e)
}
複製代碼
其中的 e
其實就是 合成事件而非瀏覽器原生事件的 event
,因此開發者無需考慮瀏覽器兼容性,只須要按照 w3c
規範取值便可,若是須要訪問原生的事件對象,能夠經過 e.nativeEvent
得到
SyntheticUIEvent
這個東西主要就是往 SyntheticMouseEvent
上加一些額外的屬性,這裏不用關心,而後這個 SyntheticMouseEvent.extend
又是由 SyntheticEvent
擴展 (extend
)來的,因此最終會 new SyntheticEvent
先看下 extend
方法:
// packages/events/SyntheticEvent.js
SyntheticEvent.extend = function(Interface) {
const Super = this;
// 原型式繼承
const E = function() {};
E.prototype = Super.prototype;
const prototype = new E();
// 構造函數繼承
function Class() {
return Super.apply(this, arguments);
}
Object.assign(prototype, Class.prototype);
Class.prototype = prototype;
Class.prototype.constructor = Class;
Class.Interface = Object.assign({}, Super.Interface, Interface);
Class.extend = Super.extend;
addEventPoolingTo(Class);
return Class;
};
複製代碼
先來了個經典的寄生組合式繼承,這種寄生方法最爲成熟,大多數庫都是使用這種繼承方法,React
這裏也用了它,讓EventConstructor
繼承於 SyntheticEvent
,得到 SyntheticEvent
上的一些屬性和方法,如前面所說的 eventPool
、getPooled
等
既然存在繼承關係,那麼 new EventConstructor
這個子類,天然就會調用父類 SyntheticEvent
的new
方法,也就是開始調用合成組件的構造器了,開始真正構造合成事件,主要就是將原生瀏覽器事件上的參數掛載到合成事件上,包括 clientX
、screenY
、timeStamp
等事件屬性, preventDefault
、stopPropagation
等事件方法,例如前面所說的經過 e.nativeEvent
得到的原生事件就是在這個時候掛載上去的:
// packages/events/SyntheticEvent.js
this.nativeEvent = nativeEvent;
複製代碼
掛載的屬性都是通過 React
處理過的,具有跨瀏覽器能力,一樣,掛載的方法也和原生瀏覽器的事件方法有所不一樣,由於此時的事件附加在 document
上的,因此調用一些事件方法,例如 e.stopPropagation()
實際上是針對 document
元素調用的,跟本來指望的元素不是同一個,那麼爲了讓合成事件的表現達到原生事件的效果,就須要對這些方法進行額外的處理
處理的方法也比較簡單,就是加了一個標誌位,例如,對於 stopPropagation
來講, React
對其進行了包裝:
// packages/events/SyntheticEvent.js
stopPropagation: function() {
const event = this.nativeEvent;
if (!event) {
return;
}
if (event.stopPropagation) {
event.stopPropagation();
} else if (typeof event.cancelBubble !== 'unknown') {
// The ChangeEventPlugin registers a "propertychange" event for
// IE. This event does not support bubbling or cancelling, and
// any references to cancelBubble throw "Member not found". A
// typeof check of "unknown" circumvents this issue (and is also
// IE specific).
event.cancelBubble = true;
}
this.isPropagationStopped = functionThatReturnsTrue;
}
複製代碼
首先就是拿到瀏覽器原生事件,而後調用對應的 stopPropagation
方法,這裏須要注意一下,這裏的 event
是由 document
這個元素上的事件觸發而生成的事件回調的參數對象,而非實際元素的事件回調的參數對象,說得明白點,就是給document
上觸發的事件,例如點擊事件,調用了一下 e.stopPropagation
,阻止事件繼續往 document
或者 Fragment
的父級傳播
// packages/events/SyntheticEvent.js
// 這個函數其實就是返回了一個 true,與此對應的,還有個函數名爲 functionThatReturnsFalse的函數,用來返回 false
function functionThatReturnsTrue() {
return true;
}
複製代碼
關鍵在於 this.isPropagationStopped = functionThatReturnsTrue;
這一句,至關因而設置了一個標誌位,對於冒泡事件來講,當事件觸發,由子元素往父元素逐級向上遍歷,會按順序執行每層元素對應的事件回調,但若是發現當前元素對應的合成事件上的 isPropagationStopped
爲 true
值,則遍歷的循環將中斷,也就是再也不繼續往上遍歷,當前元素的全部父元素的合成事件就不會被觸發,最終的效果,就和瀏覽器原生事件調用 e.stopPropagation()
的效果是同樣的
捕獲事件的原理與此相同,只不過是由父級往子級遍歷的罷了
這些事件方法(包括 stopPropagation
、preventDefault
等)通常都是在事件回調函數內調用,而事件的回調函數則是在後面的批處理操做中執行的
var event = EventConstructor.getPooled(dispatchConfig, targetInst, nativeEvent, nativeEventTarget);
accumulateTwoPhaseDispatches(event);
複製代碼
上述一大堆都是從上述代碼的第一句 getPooled
爲入口進去的,主要是爲了獲得合成事件,拿到基本的合成事件之後,開始對這個合成事件進行進一步的加工,也就是 accumulateTwoPhaseDispatches
這個方法要作的事情,這個方法涉及到的流程比較多,畫個圖清晰點:
代碼和調用的方法都比較瑣碎,但目標很清晰,就是保存當前元素及其父元素上掛在的全部事件回調函數,包括捕獲事件(captured
)和冒泡事件(bubbled
),保存到事件event
的 _dispatchListeners
屬性上,而且將當前元素及其父元素的react
實例(在 v16.x
版本中,這裏的實例是一個 FiberNode
)保存到event
的 _dispatchInstances
屬性上
拿到了全部與事件相關的元素實例以及事件的回調函數以後,就能夠對合成事件進行批量處理了
因爲
React
的事件機制比較複雜,要說的地方有點多,因此分爲了兩篇文章,剩餘分析部分請參見文章 React事件機制 - 源碼概覽(下)