React源碼解析(四):事件系統

筆者將編寫"React源碼解析"系列文章三到四篇,闡述React內部的機制。歡迎你們關注個人掘金帳號,以便能及時看到最新的文章更新推送。javascript

在前面三篇文章中,咱們闡述了react組件的構成與生命週期,setState的機制。此次咱們來談談React的事件處理。java

1.原生事件系統

咱們一般監聽真實DOM。舉🌰來講,咱們想監聽按鈕的點擊事件,那麼咱們在按鈕DOM上綁定事件和對應的回調函數便可。 遺憾的是若頁面複雜且事件處理頻率高,那麼對網頁性能是個考驗。react

2.React事件系統

react的事件處理再眼花繚亂終究仍是要回歸原生的事件系統,但它作的封裝卻很優雅。咱們直接上結論:程序員

  • React實現了SyntheticEvent層處理事件

什麼意思呢?詳細來講,React並不像原生事件同樣將事件和DOM一一對應,而是將全部的事件都綁定在網頁的document,經過統一的事件監聽器處理並分發,找到對應的回調函數並執行。按照官方文檔的說法,事件處理程序將傳遞SyntheticEvent的實例,那麼接下來咱們一探SyntheticEvent的究竟。數組

3.SyntheticEvent

1.事件註冊

上文說到,既然React對事件統一進行處理,那麼確定須要先註冊程序員寫的事件觸發函數吧?那麼這個過程是在哪裏執行的呢?由於咱們是把事件"綁定"在"組件DOM"上,例如一個點擊事件:瀏覽器

<Component onClick={this.handleClick}/>
複製代碼

其實在這個組件掛載的時候,React就已經開始經過mountCompoent內部的_updateDOMProperties方法進行事件處理了。在這個方法中,執行的是enqueuePutListener方法去註冊事件:bash

順藤摸瓜,listenTo方法關鍵調用瞭如下兩個函數:函數

  • trapBubbledEvent
  • trapCapturedEvent

熟悉原生事件系統的讀者從英文翻譯就能知道,兩個函數是用來處理事件捕獲和事件冒泡的。具體處理邏輯不分析,咱們直接看這兩個函數內部:工具

上述代碼中的target也就是document,也看到了熟悉的document.addEventListenerdocument.removeEventListener。正是這樣統一的事件綁定減小了內存的開銷。post

2.事件存儲

咱們寫的事件回調函數註冊完畢後須要存儲起來,以便觸發時進行回調。存儲的入口是EventPluginHub.putListener函數:

可見全部的回調函數都以二維數組的形式存儲在listenerBank中,根據組件對應的key來進行管理。

3.事件分發

事件註冊和事件存儲咱們已經清楚了,如今咱們看下當事件觸發時,React是如何進行事件分發和找到對應回調函數並執行的。分發入口在ReactDOMEventListener.jshandleTopLevelImpl:

上述代碼咱們理清了流程:由於事件回調函數執行後可能致使DOM結構的變化,那麼React先將當前的結構以數組的形式存儲起來,依次遍歷執行。 上述函數的_handleTopLevel最終對回調函數進行處理,看下源碼:

代碼中出現了新角色:EventPluginHub.extractEvents。查閱相關資料,得知extractEvents方法是用於合成事件的,也就是根據事件類型的不一樣,合成不一樣的跨瀏覽器的SyntheticEvent對象的實例,好比SyntheticClickEvent。而EventPluginHub顧名思義是React進行合成事件時所用的工具插件:

能夠看到對於不一樣的事件,React將使用不一樣的功能插件,這些插件都是經過依賴注入的方式進入內部使用的。React合成事件的過程很是繁瑣,但能夠歸納出extractEvents函數內部主要是經過switch函數區分事件類型並調用不一樣的插件進行處理從而生成SyntheticEvent實例。有興趣的同窗能夠自行了解。

4.事件處理

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事件回調函數能夠拿到原生的事件了。

4.總結

React事件系統爲了兼容各類版本的瀏覽器而作了大量工做,咱們沒必要鑽牛角尖去研究這些是如何實現的,與原生事件不一樣的點,只在於React對事件進行統一而不是分散的存儲與管理,捕獲事件後內部生成合成事件提升瀏覽器的兼容度,執行回調函數後再進行銷燬釋放內存,從而大大提升網頁的響應性能。

回顧:
《React源碼解析(一):組件的實現與掛載》
《React源碼解析(二):組件的類型與生命週期》
《React源碼解析(三):詳解事務與隊列》 聯繫郵箱:ssssyoki@foxmail.com

相關文章
相關標籤/搜索