<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在React@17.0.3
版本中:
id = root
的DOM元素中(網上很多說是在document
中,17
版本不是了);id = root
的DOM元素中觸發;React
自身實現了一套事件冒泡捕獲機制;React
實現了合成事件SyntheticEvent
;React
在17
版本不再使用事件池了(網上很多說使用了物件池來管理合成事件物件的建立銷燬,那是16
版本及之前);id = root
的DOM元素中委託,其實是一直在觸發的,只是沒有繫結對應的回撥函數;盜用一張官方圖,按官方解釋,之所以會將事件委託從document
中移到id = root
的DOM元素,是為了可以更加安全地進行新舊版本 React 樹的巢狀。
感興趣的可以存取:React中文網站 。
registerEvents
;listenToAllSupportedEvents
;SyntheticBaseEvent
;dispatchEvent
;事件註冊是自執行的,也就是React
自身進行呼叫的:
// 註冊React事件 registerSimpleEvents(); registerEvents$2(); registerEvents$1(); registerEvents$3(); registerEvents();
React
事件就是在元件中呼叫的onClick
這種寫法的事件。上面分為5個函數寫,主要是區分不同的事件註冊邏輯,但是最後都會新增到allNativeEvents的Set資料結構中。
這裡會註冊大部分事件,它們在React
被定義為頂級事件。
它們分為三類:
discreteEvent
,常見的如:click, keyup, change
;userBlocking
,常見的如:dragEnter, mouseMove, scroll
;continuous
,常見的如:error, progress, load,
; 它們的優先順序排序:0:離散事件, 1:使用者阻塞事件, 2:連續事件
它們會註冊冒泡和捕獲階段兩個事件。
註冊類似onMouseEnter
,onMouseLeave
單階段事件,只註冊冒泡階段事件。
註冊onChange
相關事件,註冊冒泡和捕獲階段兩個事件。
註冊onSelect
相關事件,註冊冒泡和捕獲階段兩個事件。
註冊onBeforeInput
,onCompositionUpdate
等相關事件,註冊冒泡和捕獲階段兩個事件。相關參考視訊講解:進入學習
在React原始碼系列之二:React的渲染機制曾提到過,React
在開始渲染前,會為應用建立一個fiberRoot
作為應用的根節點。在建立fiberRoot
還會做一件事,就是
listenToAllSupportedEvents(rootContainerElement);
從字面就能理解這個函數是做事件監聽的,其中rootContainerElement
引數就是應用中的id = root
的DOM元素。
該函數主要遍歷上面事件註冊新增到allNativeEvents
的事件,按照一定規則,區分冒泡階段,捕獲階段,區分有無副作用進行監聽,監聽的api還是addEventListener
:
// 監聽冒泡階段事件 function addEventBubbleListener(target, eventType, listener) { target.addEventListener(eventType, listener, false); return listener; } // 監聽捕獲階段事件 function addEventCaptureListener(target, eventType, listener) { target.addEventListener(eventType, listener, true); return listener; }
程式碼中的target
就是id = root
的DOM元素。
注意,上面監聽的listener
是一個事件派發器,並不是真實的瀏覽器事件或你寫的事件回撥函數。 不要搞混淆了。
上面提到,事件一旦在id = root
的DOM元素中委託,其實是一直在觸發的,只是沒有繫結對應的回撥函數。
意思是,當我們把滑鼠移入我們的應用頁面中時,這時就在派發事件了,因為頁面的DOM元素是有監聽mousemove
之類的事件的。
那問題來了,React
是如何得知我們給事件繫結了回撥函數並觸發對應的回撥函數的?
帶著這個問題我們來研究下事件派發。
要講事件派發,還得提下事件監聽階段監聽的listener
,它實際是下面這玩意:
function createEventListenerWrapperWithPriority(targetContainer, domEventName, eventSystemFlags) { var eventPriority = getEventPriorityForPluginSystem(domEventName); var listenerWrapper; switch (eventPriority) { case DiscreteEvent: listenerWrapper = dispatchDiscreteEvent; break; case UserBlockingEvent: listenerWrapper = dispatchUserBlockingUpdate; break; case ContinuousEvent: default: listenerWrapper = dispatchEvent; break; } return listenerWrapper.bind(null, domEventName, eventSystemFlags, targetContainer); }
和事件註冊一樣,listener
也分為dispatchDiscreteEvent, dispatchUserBlockingUpdate, dispatchEvent
三種。它們之間的主要區別是執行優先順序,還有discreteEvent
涉及到要清除之前的discreteEvent
問題,所以做了區分。但是它們最後都會呼叫dispatchEvent
。
所以事件派發的角色應該是dispatchEvent
function dispatchEvent(domEventName, eventSystemFlags, targetContainer, nativeEvent) { var allowReplay = true; allowReplay = (eventSystemFlags & IS_CAPTURE_PHASE) === 0; // 如果有離散事件正在執行,會排隊,順序執行 if (allowReplay && hasQueuedDiscreteEvents() && isReplayableDiscreteEvent(domEventName)) { domEventName, eventSystemFlags, targetContainer, nativeEvent); return; } // 嘗試事件派發,如果成功,就不用執行下面的程式碼了 var blockedOn = attemptToDispatchEvent(domEventName, eventSystemFlags, targetContainer, nativeEvent); // 嘗試事件派發成功 if (blockedOn === null) { if (allowReplay) { // 清除連續事件佇列 clearIfContinuousEvent(domEventName, nativeEvent); } return; } if (allowReplay) { if (isReplayableDiscreteEvent(domEventName)) { queueDiscreteEvent(blockedOn, domEventName, eventSystemFlags, targetContainer, nativeEvent); return; } if (queueIfContinuousEvent(blockedOn, domEventName, eventSystemFlags, targetContainer, nativeEvent)) { return; } clearIfContinuousEvent(domEventName, nativeEvent); } dispatchEventForPluginEventSystem(domEventName, eventSystemFlags, nativeEvent, null, targetContainer); }
介紹下dispatchEvent
的幾個引數:
domEventName
: DOM事件名稱,如:click
,不是onClick
;eventSystemFlags
:事件系統標記;targetContainer
:id=root
的DOM元素;nativeEvent
:原生事件(來自addEventListener
);在attemptToDispatchEvent
中, 根據nativeEvent.target
找到真正觸發事件的DOM元素,並根據DOM元素找到對應的fiber
節點,判斷fiber
節點的型別以及是否已渲染來決定是否要派發事件。
在一系列判斷通過後,就開始真正的事件處理了:
function dispatchEventsForPlugins(domEventName, eventSystemFlags, nativeEvent, targetInst, targetContainer) { // 獲取觸發事件的DOM元素 var nativeEventTarget = getEventTarget(nativeEvent); // 初始化事件派發佇列 var dispatchQueue = []; // 合成事件 extractEvents$5(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags); // 按事件派發佇列執行事件派發 processDispatchQueue(dispatchQueue, eventSystemFlags); }
在extractEvents$5
中會進行事件合成,放在下面單獨講。
在processDispatchQueue
會根據事件階段(冒泡或捕獲)來決定是正序還是倒序遍歷合成事件中的listeners
。
接下來就比較簡單了。 遍歷listeners
執行上面的listener
。
在合成事件中,會根據domEventName
來決定使用哪種型別的合成事件。
以click
為例,當我們點選頁面的某個元素時,React
會根據原生事件nativeEvent
找到觸發事件的DOM元素和對應的fiber
節點。並以該節點為孩子節點往上查詢,找到包括該節點及以上所有的click
回撥函數建立dispatchListener
,並新增到listeners
陣列中。
// dispatchListener { instance: instance, // 事件所在的fiber節點 listener: listener, // 事件回撥函數 currentTarget: currentTarget // 事件對應的DOM元素 }
當向上查詢完成後,會基於click
型別的合成事件類建立事件
// 建立合成事件範例 var _event = new SyntheticEventCtor(reactName, reactEventType, null, nativeEvent, nativeEventTarget); // 事件派發佇列新增事件 dispatchQueue.push({ event: _event, // 合成事件範例 listeners: _listeners // 同型別事件的集合陣列 });
看下SyntheticEventCtor
// Interface根據事件型別有所不同 function createSyntheticEvent(Interface) { // 合成事件建構函式 function SyntheticBaseEvent(reactName, reactEventType, targetInst, nativeEvent, nativeEventTarget) { this._reactName = reactName; this._targetInst = targetInst; this.type = reactEventType; this.nativeEvent = nativeEvent; this.target = nativeEventTarget; this.currentTarget = null; // React根據不同事件型別寫了對應的屬性介面,這裡基於介面將原生事件上的屬性clone到建構函式中 for (var _propName in Interface) {... } var defaultPrevented = nativeEvent.defaultPrevented != null ? nativeEvent.defaultPrevented : nativeEvent.returnValue === false; if (defaultPrevented) { this.isDefaultPrevented = functionThatReturnsTrue; } else { this.isDefaultPrevented = functionThatReturnsFalse; } this.isPropagationStopped = functionThatReturnsFalse; return this; } _assign(SyntheticBaseEvent.prototype, { // 阻止預設事件 preventDefault: function () {...}, // 阻止捕獲和冒泡階段中當前事件的進一步傳播 stopPropagation: function () {...}, // 合成事件不使用物件池了,這個事件是空的,沒有意義,儲存是為了向下相容不報錯。 persist: function () {}, isPersistent: functionThatReturnsTrue }); return SyntheticBaseEvent; }
看到這裡,我們基本能弄明白合成事件是個什麼東西了。
React
合成事件是將同型別的事件找出來,基於這個型別的事件,React
通過程式碼定義好的型別事件的介面和原生事件建立相應的合成事件範例,並重寫了preventDefault
和stopPropagation
方法。
這樣,同型別的事件會複用同一個合成事件範例物件,節省了單獨為每一個事件建立事件範例物件的開銷,這就是事件的合成。
事件派發分為兩個階段執行, 捕獲階段和冒泡階段。
在上面事件合成中講過,React
會根據事件觸發的fiber
節點向上查詢,將上面的同型別事件新增到佇列中,這樣天然就有了一個冒泡的順序,從最底層向上冒泡。如果倒序過來遍歷就是捕獲的順序。
所以,React
實現冒泡和捕獲就很簡單了,只需要根據事件派發的階段,判斷是冒泡階段還是捕獲階段來決定是正序遍歷listeners
還是倒序遍歷就行了。
說是講React
的合成事件,實際上講了React
的事件系統。做下總結:
React
的事件系統分為幾個部分:
1.事件註冊;
2.事件監聽;
3.事件合成;
4.事件派發; 事件系統流程:
React
程式碼執行時,內部會自動執行事件的註冊;fiberRoot
時,會進行事件的監聽,所有的事件通過addEventListener
委託在id=root
的DOM元素上進行監聽;到此這篇關於深入分析React原始碼中的合成事件的文章就介紹到這了,更多相關React合成事件內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45