React 源碼-React 事件全解

来源:https://www.cnblogs.com/99xx/archive/2022/08/22/react-source-parse-event.html
-Advertisement-
Play Games

4 運算符 4.1 算術運算符 4.1.1 概述 JavaScript 提供的算術運算符如下所示: | 類型 | 符號 | 示例| | | | | |加法運算符| + | a+b | |減法運算符| - | a-b | |乘法運算符| * | a*b | |除法運算符| / | a/b | |餘數運 ...


事件系統

react v17

事件綁定

事件綁定在函數 setInitialDOMProperties

setInitialDOMProperties 將在 complete 階段執行

function setInitialDOMProperties(
  tag: string,
  domElement: Element,
  rootContainerElement: Element | Document,
  nextProps: Object,
  isCustomComponentTag: boolean
): void {
  // *遍歷 props
  for (const propKey in nextProps) {
    if (!nextProps.hasOwnProperty(propKey)) { continue; }
    const nextProp = nextProps[propKey];
    if (...) { ... }
    // *registrationNameDependencies 包含 react 支持的所有的事件,如果當前的 propKey 是 react支持的事件就進入該 if
    else if (registrationNameDependencies.hasOwnProperty(propKey)) {
      if (nextProp != null) {
        // !註意,這裡與 react v16 有所不同,v16 這裡直接執行 ensureListeningTo 函數,但是 v17 這裡不會執行。因為 enableEagerRootListeners 是一個常量,值一直為 true,if (false) 自然不會執行,並且在 react-dom.development.js 中直接沒有這個 if,只剩下 onScroll 的判斷。這樣更能說明問題了
        if (!enableEagerRootListeners) {
          // *忽略這個函數,並沒有執行
          ensureListeningTo(rootContainerElement, propKey, domElement);
        } else if (propKey === 'onScroll') {
          listenToNonDelegatedEvent('scroll', domElement);
        }
      }
    } else if (nextProp != null) {
      setValueForProperty(domElement, propKey, nextProp, isCustomComponentTag);
    }
  }
}

那麼在 react v17 中遍歷到 onClick 這種事件的時候貌似並沒有做什麼。那麼事件綁定是什麼時候綁定的呢?其實在最開始的 createRootImpl 也就是創建 HostRootFiber 時就通過 listenToAllSupportedEvents 將所有支持的事件都綁定到了 rootContainerElement (這裡也對應了 react v17 就將事件統統綁定到 rootContainer 而不是 document)

那麼事件處理函數又是多久綁定的呢?

通過對綁定的事件處理函數進行 debugger 可以發現,其實根本沒有將事件處理函數直接綁定到 rootContainerElement 上,而是直接使用的上面 listenToAllSupportedEvents 中綁定的事件。大概的流程為:

  1. listenToAllSupportedEventsrootContainerElement 綁定所有的事件
  2. 點擊子組件,其實就相當於在點擊 rootContainerElement 所以會觸發對應的點擊事件。
  3. 綁定事件的時候會根據事件優先順序綁定不同的處理函數,但是最終其實都是執行 dispatchEvent
  4. dispatchEvent 內部將將進入其他函數,獲取觸發事件的元素,然後根據對應的 Fiber 然後在根據很多層函數,最終執行事件處理函數。

事件觸發

使用的案例

import React from 'react'
import './index.css'
class EventDemo extends React.Component{
  state = {
    count: 0,
  }

  onDemoClick = e => {this.setState({ count: this.state.count + 1 })}
  onParentClick = () => {console.log('父級元素的點擊事件被觸發了');}
  onParentClickCapture = () => {console.log('父級元素捕獲到點擊事件');}
  onSubCounterClick = () => {console.log('子元素點擊事件');}
  onSubCounterClickCapture = () => {console.log('子元素點擊事件 capture')}

  render() {
    const { count } = this.state
    return <div className={'counter-parent'} onClick={this.onParentClick} onClickCapture={this.onParentClickCapture}>
      counter-parent
      <div onClick={this.onDemoClick} className={'counter'}>
        counter:{count}
        <div className={'sub-counter'} onClick={this.onSubCounterClick} onClickCapture={this.onSubCounterClickCapture}>
          子組件
        </div>
      </div>
    </div>
  }
}

export default EventDemo
  1. 點擊子元素後,自然會執行 dispatchEvent
  2. 然後會進入 attemptToDispatchEvent
    (如果沒有正在進行的事件?因為在進入 attemptToDispatchEvent 之前會進行 hasQueuedDiscreteEvents
    hasQueuedDiscreteEvents 判斷 具體可以看 dispatchEvent)
    然後在 attemptToDispatchEvent 中會通過原生的事件參數(event)獲取到觸發事件的 DOM,然後通過該 DOM 獲取到對應的 Fiber
    然後正常情況下會進入 dispatchEventForPluginEventSystem.
  3. dispatchEventForPluginEventSystem 一般會進入批量更新,也就是 batchEventUpdates,與 render 時的一樣,也會傳入一個匿名函數,不過該匿名函數內部執行的是:dispatchEventsForPlugins.
  4. dispatchEventsForPlugins 內部又執行 extractEvents 函數
  5. extractEvents 函數內部又會使用 EventPlugin 創建 react合成事件 的 Event 參數,並且會遍歷 Fiber 鏈表,將將會觸發的事件統統放到 dispatchQueue 中(具體遍歷 Fiber 的函數是在 accumulateSinglePhaseListeners )。
    accumulateSinglePhaseListeners 具體流程如下
    1. 首先會判斷當前是 捕獲階段 還是 冒泡階段 根據階段的不同,使用不同的 reactEventName (例如:onClick 還是 onClickCapture)
    2. 然後會進入 while 迴圈,迴圈中會通過 reactEventName 獲取 instance 的事件處理函數,即 listener 如果 listener 不為 null 那麼就會將 { currentTarget, instance, listener } 放到 listeners 中(currentTarget 是當前的 dom 元素,instance 是當前的 Fiber,listener 是當前的事件處理函數),接著將 instance 指向 instance.return 繼續 while 迴圈。
    3. while 迴圈結束後,會將 listeners 返回出去。
  6. extractEvents 執行完成後,就會開始執行 dispatchQueue 中的內容了。

針對我們的案例,分析一下具體的流程

  1. 點擊子元素,那麼就會直接觸發 root 的 clickCapture 事件
  2. 進入 dispatchEvent
  3. 進入 attemptToDispatchEvent, 獲取真實觸發事件的 dom 和對應的 Fiber
  4. 進入 dispatchEventForPluginEventSystem
  5. 進入 batchEventUpdates
  6. 進入 dispatchEventsForPlugins
  7. 進入 extractEvent 獲取 react合成事件參數
  8. 進入 accumulateSinglePhaseListeners 獲取 listeners 數組因為當前是捕獲階段(在代碼中會判斷是什麼階段),所以就只會收集捕獲階段的事件處理函數(直接 push 到 listeners 並不會像小冊中說的那樣 遇到捕獲事件就 unshift 可能是版本問題),經過測試可以得知,無論案例中是否綁定了 clickCapture 都會去試圖收集捕獲階段的事件處理函數, 只是收集不到而已
  9. 返回 extractEvent 將 listeners 放到 dispatchQueue 中去
  10. 返回 dispatchEventsForPlugins 進入 processDispatchQueue 內部會判斷當前到底是什麼階段,接著迴圈 dispatchQueue
  11. 進入 processDispatchQueueItemsInOrder ,根據階段不同,按照不同的順序執行 listeners,比如捕獲階段的話,就是從後往前,冒泡階段的話就是從前往後。
  12. 再經過一系列函數的包裹,最終順利執行函數。
  13. capture 階段完成後,直接進入 bubble 階段,再次按照上面的順序執行,最終 bubble 階段也完成。就是這樣。

註意:listeners 的結構應該是 { currentTarget, instance, listener }, dispatchQueue 的結構應該是 [ { event, listeners } ] 此時的 event 應該是 React合成事件 event

註意:在此案例中,無論是捕獲階段,還是冒泡階段,因為 listeners 是一個數組(該階段將要觸發的所有 listener 數組),所以 dispatchQueue 中都只有一個元素,不清楚在上面情況下 dispatchQueue 才有多個元素。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • C# 在中國的採用需要一個殺手級應用的帶動, 那麼這樣的一個殺手級應用是 Unity嗎,我這裡大膽推測採用CoreCLR 的新一代完全採用C#構建的Unity 將是這樣的一個殺手級應用。Unity已被廣泛應用於數字孿生、數字城市、數字工廠等場景,成為各產業加速數字化轉型的一個通用技術平臺底座,而對接 ...
  • 學習內容及其引用 [ ] 委托的定義以及如何理解委托 [ ] 委托的聲明及其由來 [ ] 委托類型的實例 [ ] 多播委托 [ ] 委托的缺點 [ ] Action委托與Func委托 委托•語法篇 C#語言入門詳解 Delegate詳解 委托的定義以及如何理解委托 委托現實的定義: 本人不需要親自去 ...
  • #前言 前段時間需要在一個新項目里添加兩個後臺任務,去定時請求兩個供應商的API來同步數據;由於項目本身只是一個很小的服務,不太希望引入太重的框架,同時也沒持久化要求;於是我開始尋找在Quartz.Net、Hangfire之外,是否還有更為輕量級的框架滿足我的要求,最終我選擇了Coravel. #簡 ...
  • 由於net core 中預設沒有System.Drawing,可以通過nuget下載一個來代替System.Drawing.Common 直接壓縮圖片 /// <summary> /// 圖片壓縮 /// </summary> /// <param name="sFile">原圖片位置</param ...
  • Mac哪款三維人物動畫製作工具好用呢?DAZ Studio Pro for Mac是一款應用在Mac平臺上的3d人物動畫製作軟體,,它擁有輕鬆簡約的ui界面以及強大的虛擬化3d建模功能,通過軟體自帶的虛擬人物模型,您不僅可以自由的添加場景環境等虛擬元素,還可以進行相關主題以及光線效果的設置,用戶可以 ...
  • 小眾的2.4G射頻收發晶元, 和 Ci24R1, XN297L 一樣, 都屬於 nRF24L01 派生的 SOP8 版本. 在寄存器和操作上類似於nRF24L01, 但是寄存器中存在大量多位元組的設置, 沒有中斷, 完全靠輪詢工作, 這是這個型號的特點. 在相容性上, 和XN297L管腳佈局一致但是寄... ...
  • Oracle序列學習與使用總結 by:授客 QQ:1033553122 簡述 序列是oracle提供的用於生成一系列數字的資料庫對象,序列會自動生成順序遞增的序列號,可用於提供唯一的自動遞增主鍵。序列和視圖一樣,並不占用實際的存儲空間,只是在數據字典中保存他的定義信息。 創建序列 當創建序列時必須擁 ...
  • 概述 由一個或多個 Sentinel(哨兵)實例組成的 Sentinel 系統可以監視任意多個主伺服器,以及這些主伺服器屬下的所有從伺服器,併在被監視的主伺服器進入下線狀態時,自動將下線主伺服器屬下的某個從伺服器升級為新的主伺服器 簡單來說,哨兵就是帶有自動故障轉移功能的主從架構 搭建哨兵架構 以 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...