這是 react 事件機制的第三節 - 事件註冊,經過本文你將瞭解react 事件的註冊過程,以及在這個過程當中主要通過了哪些關鍵步驟,同時結合源碼進行驗證和加強理解。前端
文章涉及到的源碼是基於 react15.6.1版本,雖然不是最新版本可是也不會影響咱們對 react 事件機制的總體把握和理解。node
文中不會說很是細節的內容,而是會把大概的流程和原理性的內容進行介紹,作到對總體流程有個認知和理解。react
內容大綱數組
按照個人理解,react 事件註冊過程其實主要作了2件事:緩存
a. 事件註冊babel
b. 事件存儲dom
a. 事件註冊 - 組件掛載階段,根據組件內的聲明的事件類型-onclick,onchange 等,給 document 上添加事件 -addEventListener,並指定統一的事件處理程序 dispatchEvent。函數
b. 事件存儲 - 就是把 react 組件內的全部事件統一的存放到一個地方,也就是緩存起來,能夠理解成放入一個對象內,爲了在觸發事件的時候能夠查找到對應的方法去執行。this
再配個圖spa
上面大體說了事件註冊須要完成的兩個目標,那完成目標的過程須要通過哪些關鍵處理呢?
首先 react 拿到將要掛載的組件的虛擬 dom(其實就是 react dom, 相似一個對象),而後處理react dom 的 props ,判斷屬性內是否有聲明爲事件的屬性,好比onclick,這個時候獲得事件類型 click 和對應的事件處理程序 fn,而後直行後面3步
a. 執行事件註冊
b. 將react dom ,事件類型,處理函數 fn 放入數組存儲
c. 組件掛載完成後,處理 b 步驟生成的數組,通過遍歷把事件處理函數存儲到listenerBank中
再配個圖
3.1 得先從 jsx 提及
看個最熟悉的代碼,也是咱們平常的寫法
handleFatherClick=()=>{ } handleChildClick=()=>{ } render(){ return <div className="box"> <div className="father" onClick={this.handleFatherClick}> <div className="child" onClick={this.handleChildClick}>child </div> </div> </div> }
通過 babel 編譯後,能夠看到最終調用的方法是react.createElement,並且聲明的事件類型和回調也是一個props。
react.createElement執行的結果會返回一個所謂的虛擬 dom(react element 或者 react dom),看下圖
3. 2 開始處理props,拿到事件類型和回調 fn
ReactDOMComponent在進行組件加載(mountComponent)、更新(updateComponent)的時候,須要對props進行處理(_updateDOMProperties):
能夠看下 registrationNameModules 的內容,就不細說了。
3.3 註冊事件和事件的存儲
【註冊事件】
接着上面的代碼執行到了這個方法
enqueuePutListener(this, propKey, nextProp, transaction);
在這個方法裏會進行事件的註冊以及事件的存儲,包括冒泡和捕獲的處理
根據當前的組件實例獲取獲取到最高父級-也就是document,而後執行方法 listenTo - 也是最關鍵的一個方法,進行事件綁定處理
源碼文件:ReactBrowerEventEmitter.js
最後執行EventListener.listen(冒泡)或者EventListener.capture(捕獲),
單看下冒泡的註冊,其實就是addEventListener的第三個參數是 false
也能夠看到註冊事件的時候也對 ie 作了兼容。
上面沒有看到 dispatchEvent 的定義,下面能夠看到傳入 dispatchEvent 方法的代碼。
到這裏事件註冊就完事兒了。
【事件存儲】
下一步開始事件的存儲,在 react 裏全部事件的觸發都是經過 dispatchEvent方法統一進行派發的,而不是在註冊的時候直接註冊聲明的回調,來看下如何存儲的 。
【事件存儲結論】
react 把全部的事件和事件類型以及react 組件進行關聯,把這個關係保存在了一個 map裏,也就是一個對象裏(鍵值對),而後在事件觸發的時候去根據當前的組件id和事件類型查找到對應的事件。
再加個簡易圖
看源碼:
function enqueuePutListener(inst, registrationName, listener, transaction) { var containerInfo = inst._hostContainerInfo; var isDocumentFragment = containerInfo._node && containerInfo._node.nodeType === DOC_FRAGMENT_TYPE; var doc = isDocumentFragment ? containerInfo._node : containerInfo._ownerDocument; listenTo(registrationName, doc);//這個方法上面已說完 //這裏涉及到了事務,事物會在之後的章節再介紹,主要看事件註冊 //下面的代碼是將putListener放入數組,當組件掛載完後會依次執行數組的回調。也就是putListener會依次執行 transaction.getReactMountReady().enqueue(putListener, { inst: inst,//組件實例 registrationName: registrationName,//事件類型 click listener: listener //事件回調 fn }); } function putListener() { var listenerToPut = this; //放入數組,回調隊列 EventPluginHub.putListener(listenerToPut.inst, listenerToPut.registrationName, listenerToPut.listener); }
大體的流程就是執行完listenTo(事件註冊),而後執行 putListener 方法進行事件存儲,全部的事件都會存儲到一個對象中 - listenerBank,具體由EventPluginHub進行管理。
//拿到組件惟一標識 id var getDictionaryKey = function getDictionaryKey(inst) { return '.' + inst._rootNodeID; } putListener: function putListener(inst, registrationName, listener) { //獲得組件 id var key = getDictionaryKey(inst); //獲得listenerBank對象中指定事件類型的對象 var bankForRegistrationName = listenerBank[registrationName] || (listenerBank[registrationName] = {}); //存儲回調 fn bankForRegistrationName[key] = listener; //.... }
listenerBank其實就是一個二級 map,這樣的結構更方便事件的查找。
這裏的組件 id 就是組件的惟一標識,而後和fn 進行關聯,在觸發階段就能夠找到相關的事件回調。
看下listenerBank結構:
看到這個結構是否是很熟悉呢?就是咱們日常使用的 object.
到這裏大體的流程已經說完,是否是感受有點明白又不大明白。
不要緊,再來個詳細的圖,從新理解下
本文主要是從總體流程上介紹了下 react 事件中事件的註冊過程,並無深刻到源碼的細節,有興趣的小夥兒能夠自查下源碼,也但願本文可以帶給你一些啓發,若文章有表述不清或有問題的地方歡迎留言交流。
更多精彩內容歡迎關注個人公衆號-前端張大胖