筆者將編寫"React源碼解析"系列文章三到四篇,闡述React內部的機制。歡迎你們關注個人掘金帳號,以便能及時看到最新的文章更新推送。javascript
在前面三篇文章中,咱們闡述了react組件的構成與生命週期,setState的機制。此次咱們來談談React的事件處理。java
咱們一般監聽真實DOM。舉🌰來講,咱們想監聽按鈕的點擊事件,那麼咱們在按鈕DOM上綁定事件和對應的回調函數便可。 遺憾的是若頁面複雜且事件處理頻率高,那麼對網頁性能是個考驗。react
react的事件處理再眼花繚亂終究仍是要回歸原生的事件系統,但它作的封裝卻很優雅。咱們直接上結論:程序員
什麼意思呢?詳細來講,React並不像原生事件同樣將事件和DOM一一對應,而是將全部的事件都綁定在網頁的document,經過統一的事件監聽器處理並分發,找到對應的回調函數並執行。按照官方文檔的說法,事件處理程序將傳遞SyntheticEvent的實例,那麼接下來咱們一探SyntheticEvent的究竟。數組
上文說到,既然React對事件統一進行處理,那麼確定須要先註冊程序員寫的事件觸發函數吧?那麼這個過程是在哪裏執行的呢?由於咱們是把事件"綁定"在"組件DOM"上,例如一個點擊事件:瀏覽器
<Component onClick={this.handleClick}/>
複製代碼
其實在這個組件掛載的時候,React就已經開始經過mountCompoent
內部的_updateDOMProperties
方法進行事件處理了。在這個方法中,執行的是enqueuePutListener
方法去註冊事件:bash
順藤摸瓜,listenTo
方法關鍵調用瞭如下兩個函數:函數
熟悉原生事件系統的讀者從英文翻譯就能知道,兩個函數是用來處理事件捕獲和事件冒泡的。具體處理邏輯不分析,咱們直接看這兩個函數內部:工具
上述代碼中的target
也就是document
,也看到了熟悉的document.addEventListener
和document.removeEventListener
。正是這樣統一的事件綁定減小了內存的開銷。post
咱們寫的事件回調函數註冊完畢後須要存儲起來,以便觸發時進行回調。存儲的入口是EventPluginHub.putListener
函數:
可見全部的回調函數都以二維數組的形式存儲在listenerBank
中,根據組件對應的key
來進行管理。
事件註冊和事件存儲咱們已經清楚了,如今咱們看下當事件觸發時,React是如何進行事件分發和找到對應回調函數並執行的。分發入口在ReactDOMEventListener.js
的handleTopLevelImpl
:
上述代碼咱們理清了流程:由於事件回調函數執行後可能致使DOM結構的變化,那麼React先將當前的結構以數組的形式存儲起來,依次遍歷執行。 上述函數的_handleTopLevel
最終對回調函數進行處理,看下源碼:
代碼中出現了新角色:EventPluginHub.extractEvents
。查閱相關資料,得知extractEvents
方法是用於合成事件的,也就是根據事件類型的不一樣,合成不一樣的跨瀏覽器的SyntheticEvent
對象的實例,好比SyntheticClickEvent
。而EventPluginHub
顧名思義是React進行合成事件時所用的工具插件:
能夠看到對於不一樣的事件,React將使用不一樣的功能插件,這些插件都是經過依賴注入的方式進入內部使用的。React合成事件的過程很是繁瑣,但能夠歸納出extractEvents
函數內部主要是經過switch
函數區分事件類型並調用不一樣的插件進行處理從而生成SyntheticEvent
實例。有興趣的同窗能夠自行了解。
React處理事件的思想與處理setState
的思想相似,都是採用批處理的方法。在上面handleTopLevel
方法中咱們看到最後執行了runEventQueueInBatch
方法:
//事件進入隊列
EventPluginHub.enqueueEvents(events);
//...
EventPluginHub.processEventQueue(false);
複製代碼
看下processEventQueue
:
上述代碼遍歷隊列中的事件,並進入executeDispatchesAndReleaseSimulated
:
event.constructor.release(event);
複製代碼
這行代碼將React的合成事件release掉,減小內存開銷。事件處理的核心入口在executeDispatchesInOrder
:
var dispatchListeners = event._dispatchListeners;
var dispatchInstances = event._dispatchInstances;
executeDispatch(event, simulated, dispatchListeners[i], dispatchInstances[i]);
複製代碼
重要的代碼就這三行,dispatchListeners
是事件回調函數,dispatchInstances
是對應的組件,將這些參數傳入executeDispatch
後:
function executeDispatch(event, simulated, listener, inst) {
var type = event.type || 'unknown-event';
ReactErrorUtils.invokeGuardedCallback(type, listener, event);
}
複製代碼
而invokeGuardedCallback
就至關簡單了:
function invokeGuardedCallback(name, func, a) {
func(a);
}
複製代碼
上面的func(a)
其實就是listener(event)
,再往上追溯,就是dispatchListeners(dispatchInstances)
,這也就說明爲何咱們的React事件回調函數能夠拿到原生的事件了。
React事件系統爲了兼容各類版本的瀏覽器而作了大量工做,咱們沒必要鑽牛角尖去研究這些是如何實現的,與原生事件不一樣的點,只在於React對事件進行統一而不是分散的存儲與管理,捕獲事件後內部生成合成事件提升瀏覽器的兼容度,執行回調函數後再進行銷燬釋放內存,從而大大提升網頁的響應性能。
回顧:
《React源碼解析(一):組件的實現與掛載》
《React源碼解析(二):組件的類型與生命週期》
《React源碼解析(三):詳解事務與隊列》 聯繫郵箱:ssssyoki@foxmail.com