📢 文章首發博客: 阿寬的博客html
💕 舒適提示: 下邊是對React合成事件的源碼閱讀,全文有點長,可是!若是你真的想知道這鮮爲人知的背後內幕,那必定要耐心看下去!前端
最近在作一個功能,而後不當心
踩到了 React 合成事件 的坑,好奇心的驅使,去看了 React 官網合成事件 的解釋,這不看不知道,一看嚇一跳...node
SyntheticEvent是個什麼鬼?咋冒出來了個事件池?react
我就一個簡單的需求功能,爲何能扯出這些鬼玩意??git
咱們先簡單的來看一看個人需求功能是個啥???github
須要作一個彈窗打開/關閉
的功能,當點擊 button
的時候打開,此時打開的狀況下,點擊彈窗 區域
外,就須要關閉。數組
這簡單嘛,直接在 button
上註冊一個點擊事件,同時在 document.body
註冊一個點擊事件,而後在 彈窗container
裏阻止冒泡,很難嘛?瀏覽器
class FuckEvent extends React.PureComponent {
state = {
showBox: false
}
componentDidMount() {
document.body.addEventListener('click', this.handleClickBody, false)
}
componentWillUnmount() {
document.body.removeEventListener('click', this.handleClickBody, false)
}
handleClickBody = () => {
this.setState({
showBox: false
})
}
handleClickButton = () => {
this.setState({
showBox: true
})
}
render() {
return (
<div> <button onClick={this.handleClickButton}>點擊我顯示彈窗</button> {this.state.showBox && ( <div onClick={e => e.stopPropagation()}>我是彈窗</div> )} </div>
)
}
}
複製代碼
很簡單嘛,很開心的點擊了彈窗區域....緩存
因而...我沒了...點擊彈窗區域,彈窗也被關閉了。。。what the f**k ?????? 難道冒泡沒有用 ?bash
帶着這個問題,我走上了不歸之路
...
咱們都知道,什麼是事件委託,(不知道的出門左拐 👈) 在前端刀耕火種時期,事件委託但是爸爸
事件委託解決了龐大的數據列表時,無需爲每一個列表項綁定事件監聽。同時能夠動態掛載元素無需做額外的事件監聽處理。
你看,事件委託那麼牛 13,你以爲 React 會不用?呵,React 不只用了,還用的很是溜 ~
怎麼說呢,react 它接管了瀏覽器事件的優化策略,而後自身實現了一套本身的事件機制,並且特別貼心,就跟你男友同樣,它把瀏覽器的不一樣差別,都幫你消除了 ~
React 實現了一個合成事件層,就是這個事件層,把 IE 和 W3C 標準之間的兼容問題給消除了。
📌 那麼問題來了,什麼是合成事件與原生事件????
原生事件: 在 componentDidMount生命週期
裏邊進行addEventListener
綁定的事件
合成事件: 經過 JSX 方式綁定的事件,好比 onClick={() => this.handle()}
還記得上邊的那個例子嗎?咱們在彈窗的 DOM 元素上綁定了一個事件,進行阻止冒泡
{
this.state.showBox && <div onClick={e => e.stopPropagation()}>我是彈窗</div>
}
複製代碼
而後在componentDidMount生命週期
裏邊對 body 進行了 click 的綁定
componentDidMount() {
document.body.addEventListener('click', this.handleClickBody, false)
}
componentWillUnmount() {
document.body.removeEventListener('click', this.handleClickBody, false)
}
複製代碼
咱們去分析一下,由於合成事件的觸發是基於瀏覽器的事件機制來實現的,經過冒泡機制冒泡到最頂層元素,而後再由 dispatchEvent 統一去處理
回顧一下瀏覽器事件機制
Document 上邊是 Window,這裏截的是《JavaScript 高級程序設計》書籍裏的圖片
瀏覽器事件的執行須要通過三個階段,捕獲階段-目標元素階段-冒泡階段。
🙋 Question: 此時對於合成事件進行阻止,原生事件會執行嗎?答案是: 會!
📢 Answer: 由於原生事件先於合成事件執行 (我的理解: 註冊的原生事件已經執行,而合成事件處於目標階段,它阻止的冒泡只是阻止合成的事件冒泡,可是原生事件在捕獲階段就已經執行了)
React 本身實現了這麼一套事件機制,它在 DOM 事件體系基礎上作了改進,減小了內存的消耗,而且最大程度上解決了 IE 等瀏覽器的不兼容問題
那它有什麼特色?
React 上註冊的事件最終會綁定在document
這個 DOM 上,而不是 React 組件對應的 DOM(減小內存開銷就是由於全部的事件都綁定在 document 上,其餘節點沒有綁定事件)
React 自身實現了一套事件冒泡機制,因此這也就是爲何咱們 event.stopPropagation()
無效的緣由。
React 經過隊列的形式,從觸發的組件向父組件回溯,而後調用他們 JSX 中定義的 callback
React 有一套本身的合成事件 SyntheticEvent
,不是原生的,這個能夠本身去看官網
React 經過對象池的形式管理合成事件對象的建立和銷燬,減小了垃圾的生成和新對象內存的分配,提升了性能
看到這裏,應該對 React 合成事件有一個簡單的瞭解了吧,咱們接着去看一看源碼 ~
咱們在 ReactBrowserEventEmitter.js
文件中能夠看到,React 合成系統框架圖
/**
* React和事件系統概述:
*
* +------------+ .
* | DOM | .
* +------------+ .
* | .
* v .
* +------------+ .
* | ReactEvent | .
* | Listener | .
* +------------+ . +-----------+
* | . +--------+|SimpleEvent|
* | . | |Plugin |
* +-----|------+ . v +-----------+
* | | | . +--------------+ +------------+
* | +-----------.--->|EventPluginHub| | Event |
* | | . | | +-----------+ | Propagators|
* | ReactEvent | . | | |TapEvent | |------------|
* | Emitter | . | |<---+|Plugin | |other plugin|
* | | . | | +-----------+ | utilities |
* | +-----------.--->| | +------------+
* | | | . +--------------+
* +-----|------+ . ^ +-----------+
* | . | |Enter/Leave|
* + . +-------+|Plugin |
* +-------------+ . +-----------+
* | application | .
* |-------------| .
* | | .
* | | .
* +-------------+ .
* .
*/
複製代碼
源碼裏邊的一大串英文解釋,我幫大家 google 翻譯了,簡單來說就是:
Top-level delegation 用於捕獲最原始的瀏覽器事件,它主要由 ReactEventListener 負責,ReactEventListener 被注入後能夠支持插件化的事件源,這一過程發生在主線程。
React 對事件進行規範化和重複數據刪除,以解決瀏覽器的怪癖。這能夠在工做線程中完成。
將這些本地事件(具備關聯的頂級類型用來捕獲它)轉發到EventPluginHub
,後者將詢問插件是否要提取任何合成事件。
而後,EventPluginHub 將經過爲每一個事件添加「dispatches」(關心該事件的偵聽器和 ID 的序列)來對其進行註釋來進行處理。
再接着,EventPluginHub 會調度分派事件.
❗ 建議直接去看英文註釋,翻譯可能不是很標準。
看會上邊的框架圖,咱們得先知道一下這些都是個啥玩意,直接看名稱,也可以知道 :
👇 下面咱們來一步一步的看它是怎麼工做的
React 中註冊一個事件賊簡單,就好比這樣:
class TaskEvent extends Reac.PureComponent {
render() {
return (
<div onClick={() => { console.log('我是註冊事件') }} > 呵呵呵 </div>
)
}
}
複製代碼
ok,洋洋灑灑的寫下這段代碼,它是如何被註冊到 React 事件系統中的?
組件在建立 mountComponent 和更新 updateComponent 的時候,都會調用 _updateDOMProperties()
方法
📢 舒適提示,這快的源碼是 react 15.6.1 的源碼,可是我在 github 上找對應的版本進去,竟然是 Pages Not Found ... 這裏就用我翻閱資料的文章中對這個註冊事件的源碼解釋了
mountComponent: function(transaction, hostParent, hostContainerInfo, context) {
// ...
var props = this._currentElement.props;
// ...
this._updateDOMProperties(null, props, transaction);
// ...
}
複製代碼
_updateDOMProperties: function (lastProps, nextProps, transaction) {
// ...
for (propKey in nextProps) {
var nextProp = nextProps[propKey];
var lastProp = propKey === STYLE ? this._previousStyleCopy : lastProps != null ? lastProps[propKey] : undefined;
if (!nextProps.hasOwnProperty(propKey) || nextProp === lastProp || nextProp == null && lastProp == null) {
continue;
}
if (propKey === STYLE) {
// ...
} else if (registrationNameModules.hasOwnProperty(propKey)) {
// 若是是props這個對象直接聲明的屬性,而不是從原型鏈中繼承而來的,則處理它
// 對於mountComponent,lastProp爲null。updateComponent兩者都不爲null。unmountComponent則nextProp爲null
if (nextProp) {
// mountComponent和updateComponent中,enqueuePutListener註冊事件
enqueuePutListener(this, propKey, nextProp, transaction);
} else if (lastProp) {
// unmountComponent中,刪除註冊的listener,防止內存泄漏
deleteListener(this, propKey);
}
}
}
}
複製代碼
上邊的代碼很清楚告訴你,經過 enqueuePutListener()
方法進行註冊事件,咱們接着去看看這是個啥玩意
function enqueuePutListener(inst, registrationName, listener, transaction) {
if (transaction instanceof ReactServerRenderingTransaction) {
return
}
var containerInfo = inst._hostContainerInfo
var isDocumentFragment =
containerInfo._node && containerInfo._node.nodeType === DOC_FRAGMENT_TYPE
// 找到document
var doc = isDocumentFragment
? containerInfo._node
: containerInfo._ownerDocument
// 註冊事件,將事件註冊到document上
listenTo(registrationName, doc)
// 存儲事件,放入事務隊列中
transaction.getReactMountReady().enqueue(putListener, {
inst: inst,
registrationName: registrationName,
listener: listener
})
}
複製代碼
💢 看到沒,這個 enqueuePutListener()
就只幹了兩個事情 :
經過調用 listenTo
把事件註冊到 document 上 (這就是前邊說的 React 上註冊的事件最終會綁定在document
這個 DOM 上)
事務方式調用 putListener
存儲事件 (就是把 React 組件內的全部事件統一的存放到一個對象裏,緩存起來,爲了在觸發事件的時候能夠查找到對應的方法去執行)
雖說不要貼代碼,可是!直接看源碼真的是簡單明瞭啊,👉 listenTo 源碼
📢 注意,react 版本是目前 github master 分支代碼
咱們來看一下代碼
export function listenTo( registrationName: string, mountAt: Document | Element | Node ): void {
const listeningSet = getListeningSetForElement(mountAt)
const dependencies = registrationNameDependencies[registrationName]
for (let i = 0; i < dependencies.length; i++) {
const dependency = dependencies[i]
// 調用該方法進行註冊
listenToTopLevel(dependency, mountAt, listeningSet)
}
}
複製代碼
registrationName 就是傳過來的 onClick,而變量 registrationNameDependencies 是一個存儲了 React 事件名與瀏覽器原生事件名對應的一個 Map,能夠經過這個 map 拿到相應的瀏覽器原生事件名
export function listenToTopLevel( topLevelType: DOMTopLevelEventType, mountAt: Document | Element | Node, listeningSet: Set<DOMTopLevelEventType | string> ): void {
if (!listeningSet.has(topLevelType)) {
switch (topLevelType) {
//...
case TOP_CANCEL:
case TOP_CLOSE:
if (isEventSupported(getRawEventName(topLevelType))) {
trapCapturedEvent(topLevelType, mountAt) // 捕獲階段
}
break
default:
const isMediaEvent = mediaEventTypes.indexOf(topLevelType) !== -1
if (!isMediaEvent) {
trapBubbledEvent(topLevelType, mountAt) // 冒泡階段
}
break
}
listeningSet.add(topLevelType)
}
}
複製代碼
上邊忽略部分源碼,咱們看到,註冊事件的入口是 listenTo 方法, 經過對dependencies
循環調用listenToTopLevel()
方法,在該方法中調用 trapCapturedEvent 和 trapBubbledEvent 來註冊捕獲和冒泡事件。
下邊僅對 trapCapturedEvent
進行分析,👉 trapCapturedEvent 源碼地址,trapBubbledEvent 源碼地址
// 捕獲階段
export function trapCapturedEvent( topLevelType: DOMTopLevelEventType, element: Document | Element | Node ): void {
trapEventForPluginEventSystem(element, topLevelType, true)
}
// 冒泡階段
export function trapBubbledEvent( topLevelType: DOMTopLevelEventType, element: Document | Element | Node ): void {
trapEventForPluginEventSystem(element, topLevelType, false)
}
複製代碼
function trapEventForPluginEventSystem( element: Document | Element | Node, topLevelType: DOMTopLevelEventType, capture: boolean // 決定捕獲仍是冒泡階段 ): void {
let listener
switch (getEventPriority(topLevelType)) {
}
const rawEventName = getRawEventName(topLevelType)
if (capture) {
addEventCaptureListener(element, rawEventName, listener)
} else {
addEventBubbleListener(element, rawEventName, listener)
}
}
複製代碼
😝 這裏咱們就能知道,捕獲事件經過addEventCaptureListener()
,而冒泡事件經過addEventBubbleListener()
// 捕獲
export function addEventCaptureListener( element: Document | Element | Node, eventType: string, listener: Function ): void {
element.addEventListener(eventType, listener, true)
}
// 冒泡
export function addEventBubbleListener( element: Document | Element | Node, eventType: string, listener: Function ): void {
element.addEventListener(eventType, listener, false)
}
複製代碼
還記得上邊的 enqueuePutListener()
中,咱們將事件放入到事務隊列嘛?
function enqueuePutListener(inst, registrationName, listener, transaction) {
//...
// 註冊事件,將事件註冊到document上
listenTo(registrationName, doc)
// 存儲事件,放入事務隊列中
transaction.getReactMountReady().enqueue(putListener, {
inst: inst,
registrationName: registrationName,
listener: listener
})
}
複製代碼
沒錯,就是 putListener
這個玩意,咱們能夠看一下代碼
putListener: function (inst, registrationName, listener) {
// 用來標識註冊了事件,好比onClick的React對象。key的格式爲'.nodeId', 只用知道它能夠標示哪一個React對象就能夠了
// step1: 獲得組件惟一標識
var key = getDictionaryKey(inst);
// step2: 獲得listenerBank對象中指定事件類型的對象
var bankForRegistrationName = listenerBank[registrationName] || (listenerBank[registrationName] = {});
// step3: 將listener事件回調方法存入listenerBank[registrationName][key]中,好比listenerBank['onclick'][nodeId]
// 全部React組件對象定義的全部React事件都會存儲在listenerBank中
bankForRegistrationName[key] = listener;
// ...
}
// 拿到組件惟一標識
var getDictionaryKey = function (inst) {
return '.' + inst._rootNodeID;
};
複製代碼
既然事件已經委託註冊到 document
上了,那麼事件觸發的時候,確定須要一個事件分發的過程,流程也很簡單,既然事件存儲在 listenrBank
中,那麼我只須要找到對應的事件類型,而後執行事件回調就 ok 了
📢 注意: 因爲元素自己並無註冊任何事件,而是委託到了 document 上,因此這個將被觸發的事件是 React 自帶的合成事件,而非瀏覽器原生事件
首先找到事件觸發的DOM
和React Component
,找真實的 DOM 仍是很好找的,在getEventTarget 源碼中能夠看到:
// 源碼看這裏: https://github.com/facebook/react/blob/master/packages/react-dom/src/events/ReactDOMEventListener.js#L419
const nativeEventTarget = getEventTarget(nativeEvent)
let targetInst = getClosestInstanceFromNode(nativeEventTarget)
複製代碼
function getEventTarget(nativeEvent) {
let target = nativeEvent.target || nativeEvent.srcElement || window
if (target.correspondingUseElement) {
target = target.correspondingUseElement
}
return target.nodeType === TEXT_NODE ? target.parentNode : target
}
複製代碼
這個 nativeEventTarget
對象上掛在了一個以 __reactInternalInstance
開頭的屬性,這個屬性就是 internalInstanceKey
,其值就是當前 React 實例對應的 React Component
繼續看源碼: dispatchEventForPluginEventSystem()
function dispatchEventForPluginEventSystem( topLevelType: DOMTopLevelEventType, eventSystemFlags: EventSystemFlags, nativeEvent: AnyNativeEvent, targetInst: null | Fiber ): void {
const bookKeeping = getTopLevelCallbackBookKeeping(
topLevelType,
nativeEvent,
targetInst,
eventSystemFlags
)
try {
// Event queue being processed in the same cycle allows
// `preventDefault`.
batchedEventUpdates(handleTopLevel, bookKeeping)
} finally {
releaseTopLevelCallbackBookKeeping(bookKeeping)
}
}
複製代碼
看到了嘛,batchedEventUpdates()
批量更新,它的工做是把當前觸發的事件放到了批處理隊列中。handleTopLevel 是事件分發的核心所在
👉 源碼在這裏: handleTopLevel
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)
}
複製代碼
這裏直接看上邊的英文註釋,講的很清楚,主要就是事件回調可能會改變 DOM 結構,因此要先遍歷層次結構,以防存在任何嵌套的組件,而後緩存起來。
而後繼續這個方法
for (let i = 0; i < bookKeeping.ancestors.length; i++) {
targetInst = bookKeeping.ancestors[i]
// getEventTarget上邊有講到
const eventTarget = getEventTarget(bookKeeping.nativeEvent)
const topLevelType = ((bookKeeping.topLevelType: any): DOMTopLevelEventType)
const nativeEvent = ((bookKeeping.nativeEvent: any): AnyNativeEvent)
runExtractedPluginEventsInBatch(
topLevelType,
targetInst,
nativeEvent,
eventTarget,
bookKeeping.eventSystemFlags
)
}
複製代碼
這裏就是一個 for 循環來遍歷這個 React Component 及其全部的父組件,而後執行runExtractedPluginEventsInBatch()
方法
從上面的事件分發中可見,React 自身實現了一套冒泡機制。從觸發事件的對象開始,向父元素回溯,依次調用它們註冊的事件 callback。
上邊講到的 runExtractedPluginEventsInBatch()
方法就是事件執行的入口了,經過源碼,咱們能夠知道,它幹了兩件事
👉 runExtractedPluginEventsInBatch 源碼
export function runExtractedPluginEventsInBatch( topLevelType: TopLevelType, targetInst: null | Fiber, nativeEvent: AnyNativeEvent, nativeEventTarget: EventTarget, eventSystemFlags: EventSystemFlags ) {
// step1 : 構造合成事件
const events = extractPluginEvents(
topLevelType,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags
)
// step2 : 批處理
runEventsInBatch(events)
}
複製代碼
咱們來看看相關的代碼 extractPluginEvents()
和 runEventsInBatch()
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
}
複製代碼
首先會去遍歷 plugins
,相關代碼在: plugins 源碼,這個 plugins 就是全部事件合成 plugins 的集合數組,這些 plugins 是在 EventPluginHub
初始化時候注入的
// 📢 源碼地址 : https://github.com/facebook/react/blob/master/packages/legacy-events/EventPluginHub.js#L80
export const injection = {
injectEventPluginOrder,
injectEventPluginsByName
}
複製代碼
// 📢 源碼地址 : https://github.com/facebook/react/blob/master/packages/react-dom/src/client/ReactDOMClientInjection.js#L26
EventPluginHubInjection.injectEventPluginOrder(DOMEventPluginOrder)
EventPluginHubInjection.injectEventPluginsByName({
SimpleEventPlugin: SimpleEventPlugin,
EnterLeaveEventPlugin: EnterLeaveEventPlugin,
ChangeEventPlugin: ChangeEventPlugin,
SelectEventPlugin: SelectEventPlugin,
BeforeInputEventPlugin: BeforeInputEventPlugin
})
複製代碼
打住,這裏不展開分析,咱們繼續看extractEvents
的邏輯代碼
const extractedEvents = possiblePlugin.extractEvents(
topLevelType,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags
)
if (extractedEvents) {
events = accumulateInto(events, extractedEvents)
}
複製代碼
由於 const possiblePlugin: PluginModule = plugins[i], 類型是 PluginModule,咱們能夠去 👉SimpleEventPlugin 源碼去看一下 extractEvents
到底幹了啥
extractEvents: function() {
const dispatchConfig = topLevelEventsToDispatchConfig[topLevelType]
if (!dispatchConfig) {
return null
}
//...
}
複製代碼
首先,看下 topLevelEventsToDispatchConfig
這個對象中有沒有 topLevelType 這個屬性,只要有,那麼說明當前事件可使用 SimpleEventPlugin
構造合成事件
函數裏邊定義了 EventConstructor
,而後經過 switch...case
語句進行賦值
extractEvents: function() {
//...
let EventConstructor
switch (topLevelType) {
// ...
case DOMTopLevelEventTypes.TOP_POINTER_UP:
EventConstructor = SyntheticPointerEvent
break
default:
EventConstructor = SyntheticEvent
break
}
}
複製代碼
總之就是賦值給 EventConstructor
,若是你想更加了解SyntheticEvent
,請點擊這裏
設置好了EventConstructor
以後,這個方法繼續執行
extractEvents: function() {
//...
const event = EventConstructor.getPooled(
dispatchConfig,
targetInst,
nativeEvent,
nativeEventTarget
)
accumulateTwoPhaseDispatches(event)
return event
}
複製代碼
這一段代碼的意思就是,從 event 對象池中取出合成事件,這裏的 getPooled()
方法其實在在 SyntheticEvent
初始化的時候就被設置好了,咱們來看一下代碼
function addEventPoolingTo(EventConstructor) {
EventConstructor.eventPool = []
// 就是這裏設置了getPooled
EventConstructor.getPooled = getPooledEvent
EventConstructor.release = releasePooledEvent
}
SyntheticEvent.extend = function(Interface) {
//...
addEventPoolingTo(Class)
return Class
}
addEventPoolingTo(SyntheticEvent)
複製代碼
看到這裏,咱們知道,getPooled
就是 getPooledEvent
,那咱們去看看getPooledEvent
作了啥玩意
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
)
}
複製代碼
首先呢,會先去對象池中,看一下 length 是否爲 0,若是是第一次事件觸發,那很差意思,你須要 new EventConstructor
了,若是後續再次觸發事件的時候,直接從對象池中取,也就是直接 instance = EventConstructor.eventPool.pop()
出來的完事了
ok,咱們暫時就講到這,咱們繼續說一說事件執行的另外一個重要操做: 批處理 runEventsInBatch(events)
批處理主要是經過 runEventQueueInBatch(events)
進行操做,咱們來看看源碼: 👉 runEventQueueInBatch 源碼
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()
}
複製代碼
這個方法首先會將當前須要處理的 events 事件,與以前沒有處理完畢的隊列調用 accumulateInto
方法按照順序進行合併,組合成一個新的隊列
若是processingEventQueue
這個爲空,gg,沒有處理的事件,退出,不然調用 forEachAccumulated()
,源碼看這裏: forEachAccumulated 源碼
function forEachAccumulated<T>( arr: ?(Array<T> | T), cb: (elem: T) => void, scope: ?any ) {
if (Array.isArray(arr)) {
arr.forEach(cb, scope)
} else if (arr) {
cb.call(scope, arr)
}
}
複製代碼
這個方法就是先看下事件隊列 processingEventQueue
是否是個數組,若是是數組,說明隊列中不止一個事件,則遍歷隊列,調用 executeDispatchesAndReleaseTopLevel
,不然說明隊列中只有一個事件,則無需遍歷直接調用便可
📢 executeDispatchesAndReleaseTopLevel 源碼
const executeDispatchesAndRelease = function(event: ReactSyntheticEvent) {
if (event) {
executeDispatchesInOrder(event)
if (!event.isPersistent()) {
event.constructor.release(event)
}
}
}
const executeDispatchesAndReleaseTopLevel = function(e) {
return executeDispatchesAndRelease(e)
}
複製代碼
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
}
複製代碼
首先對拿到的事件上掛載的 dispatchListeners
,就是全部註冊事件回調函數的集合,遍歷這個集合,若是event.isPropagationStopped() = ture
,ok,break 就行了,由於說明在此以前觸發的事件已經調用 event.stopPropagation()
,isPropagationStopped 的值被置爲 true,當前事件以及後面的事件做爲父級事件就不該該再被執行了
這裏當 event.isPropagationStopped()爲 true 時,中斷合成事件的向上遍歷執行,也就起到了和原生事件調用 stopPropagation 相同的效果 若是循環沒有被中斷,則繼續執行 executeDispatch
方法,至於這個方法,源碼地址獻上: executeDispatch 源碼地址
還有...
沒有後續了,寫不動了,接下來你們自行去看源碼吧,從中午看踩坑,而後經過 event.nativeEvent.stopImmediatePropagation
解決問題以後,就開始翻閱相關博客文章,去看源碼,我炸了,中午 2 點到晚上 10 點,都在看這玩意,我已經吐了,OMG