React事件系统研究总结
React作為目前前端業界最流行的mvvm框架之一已經被廣大前端同學所熟知,而在日常工作中已經熟悉使用React的我們對React內部的工作流程、設計理念是否又有足夠的了解呢?本文是對于React事件(DOM)系統的研究。
?
●●●?
React事件系統概況
1.1React的事件處理
熟練使用React的前端同學相信對于以下這段代碼并會不陌生:
上面這張圖是一個從React官網借來的事件處理的例子,在React中想要讓某些元素監聽事件的方法就是在元素上(在這里是<a>)設置想要監聽的事件(onClick)以及事件觸發后的回調函數(handleClick), 而真的事件一旦發生后React是如何處理并觸發事件回調的呢,我們今天就要研究其內部的工作機制。
?
1.2 合成事件
在上面那張圖里handleClick函數的參數e并不是我們通常以為的DOM原生事件,而是React事件系統獨有的概念:合成事件SyntheticEvent,合成事件是React對原生事件(DOM)的抽象封裝,合成事件的屬性包括
nativeEvent 合成事件對應的原生事件
preventDefault() 阻止默認行為
stopPropagation() 阻止事件傳播
currentTarget 當前事件處理的元素
target 觸發事件的元素
等其他屬性和方法
而React之所以要創造合成事件這個新的概念,其主要原因是要抹平跨瀏覽器差異,因為不同的瀏覽器的事件機制處理不盡相同,所以React需要這一層額外的封裝來統一React的事件處理行為;此外,React的事件系統還使用了事件池來存放事件以提高性能,減少內存開銷。
?
1.3 React事件系統總覽
我們先來看看React官方對于事件系統工作流程的描述,下圖來自官方源碼:?https://github.com/facebook/react/blob/master/packages/react-dom/src/events/ReactBrowserEventEmitter.js
通過上圖我們大致可以知道,React事件系統由幾部分構成:
ReactEventListener負責監聽DOM事件
ReactEventEmitter負責將事件傳送給EventPluginHub
EventPluginHub(事件插件容器的容器,這個容器負責容納各種所謂的事件插件(EventPlugin)
EventPlugin的工作就是將原生事件轉換為React自定義的合成事件(SyntheticEvent),并搜集這個事件都有哪些元素在監聽和監聽的回調,最后在應用層級(application)觸發回調。
現在我們對React事件系統已經有了一個抽象的了解,下面我們具體看看React是如何完成從綁定監聽、事件觸發到調用回調這樣一個完整的流程的。
●●●?
React事件系統工作機制
2.1 React源碼事件系統相關代碼介紹
在進入介紹React事件系統工作機制之前,我們先了解一下React源碼中哪些部分在此次的討論范圍之內(以2018-04-13 v16.3版本為準)
packages/events是事件系統的核心代碼,內含合成事件與EventPlugin相關模塊,是跨平臺(DOM,ReactNative)通用的抽象事件處理相關代碼
packages/react-dom/src/client?包含React Virtual DOM相關的核心代碼
packages/react-dom/src/events?包含與DOM相關的事件處理代碼,包括DOM的事件監聽器ReactDOMEventListener等等。?
下面來介紹React事件系統的具體工作流程
2.2 綁定監聽階段
在早先的前端開發年代,我們想要監聽某個元素上的事件通常會直接給這個元素加上事件監聽(或者通過給其母元素加上事件監聽代理),但無論如何事件監聽都設置在了該元素或者該元素相近元素的身上,React監聽DOM事件的邏輯則不同,React會在Document層面統一監聽事件,而不會在具體元素上添加eventListener,這樣避免了在UI復雜的情況下要給數量眾多的DOM元素增加eventListener的過程,React統一在Document層面監聽事件并尋找各個監聽了該事件的Component,再執行它們的回調。而React綁定事件監聽的流程如下
(1).在React準備渲染階段,ReactDOMFiberComponent.js(Virtual DOM的最小獨立單元Fiber)會調用setInitialDOMProperties方法初始化React元素的屬性
(2).setInitialDOMProperties方法里如果發現這個組件設置了監聽事件就會調用ensureListeningTo方法來監聽這個事件(具體的監聽綁定不在此處)
(3).ensureListeningTo會調用ReactBrowserEventEmitter模塊的listenTo方法來進行對具體事件的監聽,listenTo方法會根據事件類型來決定監聽是在捕獲還是冒泡階段。(scroll,focus,blur,cancel,close事件監聽捕獲階段,其他時間監聽在冒泡階段)
(4).listenTo方法調用ReactDOMEventListener模塊的trapCapturedEvent/trapBubbledEvent方法進行監聽
(5).trapCapturedEvent/trapBubbledEvent會調用EventListener.js的addEventBubbleListener/addEventCaptureListener方法進行DOM層級的事件監聽,而監聽的回調是一個叫做dispatchEvent的方法,dispatchEvent是DOM原生事件觸發后React事件處理流程的起點.
至此,React時間系統的事件監聽綁定完成.
?
2.3 事件觸發至生成合成事件階段
當DOM的原生事件觸發后,React事件系統會如何運轉呢,上一部分提到ReactDOMEventListener的dispatchEvent方法會被觸發,以下是事件觸發后的流程
(1).原生事件被觸發:比如,你點擊了一個button
(2).ReactDOMEventListener的dispatchEvent方法會收到原生事件,并將原生事件和將觸發事件元素所在的虛擬DOM節點等其他信息組合成一個叫Bookkeeping的對象
(3).ReactDOMEventListener會調用handleTopLevel方法來處理Bookkeeping對象,在handleTop方法內調用EventPluginHub.js的runExtractedEventsInBatch方法,此時事件處理的任務讓渡給EventPluginHub(如1.3圖中所示)
(4).EventPluginHub的runExtractedEventsInBatch方法會調用extractEvents方法,extractEvents函數的作用就是根據原生事件等信息生成相應的合成事件
(5).extractEvents函數的執行過程中會遍歷所有該原生事件相關的事件插件(EventPlugin),讓每一個EventPlugin生成對應的合成事件,并將所有合成事件放在一個數組里
(6).EventPlugin在生成合成事件的過程中收集所有用戶編寫的事件監聽回調(比如1中的handleClick)
這一階段完成了從原生事件觸發到生成合成事件(收集需要執行的回調函數)的工作.
?
2.4 執行回調階段
EventPluginHub在獲得了某一原生事件相關的一個或多個合成事件后,開始執行這些事件的回調,其具體流程如下
(1).EventPluginHub對合成事件數組執行runEventsInBatch方法,該方法的作用是處理合成事件數組
(2).runEventsInBatch會對合成事件隊列里的每一個合成事件執行executeDispatchesAndReleaseTopLevel,這個方法會先調用EventPluginUtils模塊的executeDispatchesInOrder,并且在之后將合成事件釋放(除非用戶手動選擇將該合成事件持久化)
(3).EventPluginUtils的executeDispatchesInOrder會找到所有監聽這個事件的React元素(FiberNode)和回調(比如handleClick),然后執行每一個回調.
至此React事件系統完整的處理了一個事件監聽,用戶編寫的事件監聽回調執行完畢.
?重新快速梳理一遍React事件系統的工作流程就是
ReactDOMFiberComponent模塊初始化Virtual DOM節點的屬性
ReactBrowserEventEmitter/ReactDOMEventListener/EventListener模塊會對用戶設置的事件進行監聽
原生事件觸發
ReactDOMEventListener將原生事件交給EventPluginHub處理
EventPluginHub讓各個相關的EventPlugin生成合成事件
EventPluginHub讓合成事件隊列里的各個合成事件執行相應的回調
?
●●●?
React事件系統思考題
3.1 React事件和原生事件回調的執行順序
Q: 如果給一個button設置了onClick回調handleClick1,又手動給這個button的點擊事件手動設置addEventListenerhandleClick2,如果點擊這個按鈕,哪個回調先執行?
A: handleClick2先執行,handleClick1再執行,因為React在Document層級冒泡階段監聽click事件,所以當原生事件在button上先觸發handleClick2上再冒泡到document上,然后React再啟動自有的事件處理流程.
追加問題:如果是blur,focus等不冒泡的事件,比如給input設置onBlur,又給input手動添加blur的eventListener,當事件觸發時哪個先執行?
?
3.2 阻止冒泡的處理
Q: 如圖有如下組件, onBtnClick里調用了合成事件的阻止冒泡方法stopPropagation,那么圖中button的母元素div綁定的兩個事件回調onDivNativeClick和onDivClick是否會執行?
想知道答案的同學可以去https://codesandbox.io/s/1q8n95oovq試試
?
3.3 React在Document層級監聽事件,如果有多個元素(不在同一條子樹上)綁定同一事件,React如何準確觸發回調?
在2(6)步驟中,會調用accumulateTwoPhaseDispatches函數,這個函數會模擬事件的捕獲/冒泡流程,收集virtual dom中事件觸發的元素到根元素之間路徑上每個元素針對這個事件的回調,只有在這里收集到的回調才會稍后被執行,所以不存在會誤觸發其他監聽同樣事件元素的回調.
?
以上是此次React事件系統的研究,為了研究React內部實現的工程細節所以討論的粒度較細,謝謝閱讀!
?
參考資料
React官網: 事件處理 ? ? https://reactjs.org/docs/handling-events.html
React官網: 合成事件?https://reactjs.org/docs/events.html
React源碼: https://github.com/facebook/react
https://levelup.gitconnected.com/how-exactly-does-react-handles-events-71e8b5e359f2
https://www.kirupa.com/react/events_in_react.htm
?
——推薦閱讀——
網易云信IM小程序上線?我們是這么做的!>>
全面復盤!深度剖析直播答題產品架構的難點與坑>>
如何快速設計短信驗證碼>>
如何做好Android 端音視頻測試>>
總結
以上是生活随笔為你收集整理的React事件系统研究总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Wireshark对HTTPS数据的解密
- 下一篇: 高并发IM系统架构优化实践