React-事件机制杂记
前提
最近通過閱讀React官方文檔的事件模塊,有了一些思考和收獲,在這里記錄一下~
調用方法時需要手動綁定this
先從一段官方代碼看起:
代碼中的注釋提到了一句話:
This binding is necessary to make this work in the callbackthis的綁定是必須的,其實這一塊是比較容易理解的, 因為這并不是React的一個特殊點, 而是Javascript這門語言的特性。
可以看到,調用的是this.handleClick函數,handleClick函數里面又讀取到了this屬性,但是該函數的調用位置又是在render函數里面,render返回的是一個JSX,最后經過babel編譯成調用React.createElement函數,
在這之前,我們掌握的是this永遠指向的是最后調用它的對象,經過這樣的一個轉換, 實際上this最后指向的是undeined了, 那么調用handleClick函數自然會報錯。
當然,如果你不在函數里面使用this的話,通常會沒事,但并不建議這么做。
關于this的指向與function的原理,推薦閱讀 how functions work in JavaScript
既然知道了是因為this的指向原因而采用綁定的做法,那當然可以用箭頭函數來解決了,箭頭函數中的this是在定義函數的時候綁定,也就是說this是繼承自父執行上下文,如下:
這樣this也能達到我們的預期效果
合成事件SyntheticEvent
先從官方上的一段話看起,他的意思是合成事件是React根據W3C標準定義的,無需擔心瀏覽器之間的差異
Here, e is a synthetic event. React defines these synthetic events according to the W3C spec, so you don’t need to worry about cross-browser compatibility樣看起來React的合成事件只是兼容瀏覽器? 答案當然是遠遠不止啦!
在探尋其優點之前,我們先看一下其是怎樣的一個機制。
React的事件機制其實網上有很多同學都分析過了, 他并沒有將事件注冊在對應的元素或者組件上面,而是通過委托的方式,將所有的事件都注冊到了document對象上,并統一調用一個dispatch回調函數,其流程圖如下
我們也可以從一個實際的簡單例子看看:
我們把回調函數綁定到了button上,但是在事件上卻沒有看到button元素, 但是卻有document,并且可以看到他的回調函數就是dispatchInteractiveEvent
最后觸發事件的回調函數時,在原生的DOM會傳入一個事件屬性event,但是因為React將 所有事件委托給document處理, 那么這個event就和我們想要的不一樣,如target指向的是document,于是React就有了自己的一個合成事件,通過一個叫SyntheticEvent的基類來生成所需要的事件屬性,并傳入回調函數作為方法。
說到底,React就是把所有事件委托給document處理, 那么這樣做有什么好處:
可以統一在組件掛載和卸載時做處理
只需要注冊一個事件即可,節省內存開銷
可以手動控制事件流程,特別是對state的batch處理(參考React系列的setState)
- 可以統一在組件掛載和卸載時做處理
- 只需要注冊一個事件即可,節省內存開銷
- 可以手動控制事件流程,特別是對state的batch處理(參考React系列的setState)
事件屬性會在事件調用后被回收,即不能異步訪問
老規矩,先上一段代碼:
可以看到在setTimeout函數中,訪問事件屬性是null。這是為啥?
其實這也是合成事件的一個優化手段。 React會在事件調用完成后清理掉屬性,否則每點擊一次就生成一個事件,那么內存的開銷會越來越大,具體的代碼可以在后面的源碼分析中看到:
當然了, React也可以手動設置不回收,如下:
If you want to access the event properties in an asynchronous way, you should call event.persist() on the event我們可以通過調用event,persist來設置不回收。
事件機制的源碼分析
注冊階段
首先在某一個任務單元fiber調用compeleteWork函數時, React會判斷其是否具有事件屬性, 如果有則調用ensureListeningTo函數
ensureListeningTo函數主要是獲取到document對象, 并調用listenTo函數
listerTo函數 主要是通過調用trapBubbledEvent或者trapCapturedEvent將事件放在document事件上監聽
trapBubbledEvent主要是監聽事件, 但也可以看出, 所有事件最后觸發的都是注冊在document上的dispatch函數
調用階段
dispatch函數, 主要是獲取實際觸發的元素以及對應的fiber, 最后調用batchedUpdates函數, batchedUpdates函數里面的邏輯主要是關于setState的,這里主要是看事件機制, 只要知道最后調用的是handleTopLevel(bookkeeping)就好
handleTopLevel函數主要是拿到需要觸發事件的相關fiber, 并調用runExtractedEventsInBatch函數
extractEvents函數是一個生成React事件的函數,React事件是通過繼承一個通用類SyntheticEvent生成的,如一個鼠標事件的生成
React事件內部做了優化, 只要生成過SyntheticMouseEvent類, 就會再釋放事件的時候將這個類存儲起來,在下一個事件觸發時可以直接使用
React生成事件后, 會調用accumulateTwoPhaseDispatches(event)函數,該函數一直追溯下去, 最后會調用traverseTwoPhase函數,
traverseTwoPhase函數主要是獲取祖先組件的fiber, 并進行捕獲和冒泡的階段處理
accumulateDirectionalDispatches函數相對簡單, 就是把fiber上對應的事件函數賦值給evnet的_dispatchListeners屬性
React事件獲取完成后, 回到runExtractedEventsInBatch函數繼續調用runEventsInBatch(events, false); 函數的中間作了一系列的處理, 但最后執行的是executeDispatchesAndRelease函數
executeDispatchesAndRelease函數會在執行完事件后判斷用戶是否有設置不銷毀事件, 如果沒有, 則銷毀事件并保存事件類, 一個事件類實例一次并重復使用, 這也是為什么官方提到事件屬性只能在當前循環中讀到
繼續往下走, 最后執行的函數是invokeGuardedCallbackDev, 該函數通過注冊一個自定義的元素<react>和自定義的事件, 并觸發它來達到執行回調函數的功能
流程總結
總結
以上是生活随笔為你收集整理的React-事件机制杂记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 腾讯广点通这三年
- 下一篇: sql server(常用)