亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

深入分析React源碼中的合成事件

 更新時間:2022年11月10日 14:59:33   作者:goClient1992  
合成事件不是瀏覽器本身觸發(fā)的事件,自己創(chuàng)建和觸發(fā)的事件。本文將從源碼角度帶大家一起深入了解下React中的合成事件,需要的可以參考一下

熱身準備

明確幾個概念

React@17.0.3版本中:

  • 所有事件都是委托在id = root的DOM元素中(網(wǎng)上很多說是在document中,17版本不是了);
  • 在應用中所有節(jié)點的事件監(jiān)聽其實都是在id = root的DOM元素中觸發(fā);
  • React自身實現(xiàn)了一套事件冒泡捕獲機制;
  • React實現(xiàn)了合成事件SyntheticEvent;
  • React17版本不再使用事件池了(網(wǎng)上很多說使用了對象池來管理合成事件對象的創(chuàng)建銷毀,那是16版本及之前);
  • 事件一旦在id = root的DOM元素中委托,其實是一直在觸發(fā)的,只是沒有綁定對應的回調(diào)函數(shù);

盜用一張官方圖,按官方解釋,之所以會將事件委托從document中移到id = root的DOM元素,是為了可以更加安全地進行新舊版本 React 樹的嵌套

感興趣的可以訪問:React中文網(wǎng)站 。

事件系統(tǒng)角色劃分

  • 事件注冊:registerEvents
  • 事件監(jiān)聽:listenToAllSupportedEvents;
  • 事件合成:SyntheticBaseEvent;
  • 事件派發(fā):dispatchEvent;

事件注冊

事件注冊是自執(zhí)行的,也就是React自身進行調(diào)用的:

// 注冊React事件
registerSimpleEvents();  
registerEvents$2();
registerEvents$1();
registerEvents$3();
registerEvents();

React事件就是在組件中調(diào)用的onClick這種寫法的事件。上面分為5個函數(shù)寫,主要是區(qū)分不同的事件注冊邏輯,但是最后都會添加到allNativeEvents的Set數(shù)據(jù)結(jié)構(gòu)中。

registerSimpleEvents

這里會注冊大部分事件,它們在React被定義為頂級事件。

它們分為三類:

  • 離散事件:discreteEvent,常見的如:click, keyup, change;
  • 用戶阻塞事件:userBlocking,常見的如:dragEnter, mouseMove, scroll
  • 連續(xù)事件:continuous,常見的如:error, progress, load, ; 它們的優(yōu)先級排序:

0:離散事件, 1:用戶阻塞事件, 2:連續(xù)事件

它們會注冊冒泡和捕獲階段兩個事件。

registerEvents$2

注冊類似onMouseEnter,onMouseLeave單階段事件,只注冊冒泡階段事件。

registerEvents$1

注冊onChange相關事件,注冊冒泡和捕獲階段兩個事件。

registerEvents$3

注冊onSelect相關事件,注冊冒泡和捕獲階段兩個事件。

registerEvents

注冊onBeforeInput,onCompositionUpdate等相關事件,注冊冒泡和捕獲階段兩個事件。相關參考視頻講解:進入學習

事件監(jiān)聽

在React源碼系列之二:React的渲染機制曾提到過,React在開始渲染前,會為應用創(chuàng)建一個fiberRoot作為應用的根節(jié)點。在創(chuàng)建fiberRoot還會做一件事,就是

listenToAllSupportedEvents(rootContainerElement);

從字面就能理解這個函數(shù)是做事件監(jiān)聽的,其中rootContainerElement參數(shù)就是應用中的id = root的DOM元素。

該函數(shù)主要遍歷上面事件注冊添加到allNativeEvents的事件,按照一定規(guī)則,區(qū)分冒泡階段,捕獲階段,區(qū)分有無副作用進行監(jiān)聽,監(jiān)聽的api還是addEventListener:

// 監(jiān)聽冒泡階段事件
function addEventBubbleListener(target, eventType, listener) {
  target.addEventListener(eventType, listener, false);
  return listener;
}
// 監(jiān)聽捕獲階段事件
function addEventCaptureListener(target, eventType, listener) {
  target.addEventListener(eventType, listener, true);
  return listener;
}

代碼中的target就是id = root的DOM元素。

注意,上面監(jiān)聽的listener是一個事件派發(fā)器,并不是真實的瀏覽器事件或你寫的事件回調(diào)函數(shù)。 不要搞混淆了。

事件派發(fā)

上面提到,事件一旦在id = root的DOM元素中委托,其實是一直在觸發(fā)的,只是沒有綁定對應的回調(diào)函數(shù)。

意思是,當我們把鼠標移入我們的應用頁面中時,這時就在派發(fā)事件了,因為頁面的DOM元素是有監(jiān)聽mousemove之類的事件的。

那問題來了,React是如何得知我們給事件綁定了回調(diào)函數(shù)并觸發(fā)對應的回調(diào)函數(shù)的?

帶著這個問題我們來研究下事件派發(fā)

要講事件派發(fā),還得提下事件監(jiān)聽階段監(jiān)聽的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三種。它們之間的主要區(qū)別是執(zhí)行優(yōu)先級,還有discreteEvent涉及到要清除之前的discreteEvent問題,所以做了區(qū)分。但是它們最后都會調(diào)用dispatchEvent。

所以事件派發(fā)的角色應該是dispatchEvent

function dispatchEvent(domEventName, eventSystemFlags, targetContainer, nativeEvent) {

  var allowReplay = true;

  allowReplay = (eventSystemFlags & IS_CAPTURE_PHASE) === 0;
  // 如果有離散事件正在執(zhí)行,會排隊,順序執(zhí)行
  if (allowReplay && hasQueuedDiscreteEvents() && isReplayableDiscreteEvent(domEventName)) {
    domEventName, eventSystemFlags, targetContainer, nativeEvent);
    return;
  }
  // 嘗試事件派發(fā),如果成功,就不用執(zhí)行下面的代碼了
  var blockedOn = attemptToDispatchEvent(domEventName, eventSystemFlags, targetContainer, nativeEvent);
  // 嘗試事件派發(fā)成功
  if (blockedOn === null) {
    if (allowReplay) {
      // 清除連續(xù)事件隊列
      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的幾個參數(shù):

  • domEventName: DOM事件名稱,如:click,不是onClick;
  • eventSystemFlags:事件系統(tǒng)標記;
  • targetContainerid=root的DOM元素;
  • nativeEvent:原生事件(來自addEventListener);

attemptToDispatchEvent中, 根據(jù)nativeEvent.target找到真正觸發(fā)事件的DOM元素,并根據(jù)DOM元素找到對應的fiber節(jié)點,判斷fiber節(jié)點的類型以及是否已渲染來決定是否要派發(fā)事件。

在一系列判斷通過后,就開始真正的事件處理了:

function dispatchEventsForPlugins(domEventName, eventSystemFlags, nativeEvent, targetInst, targetContainer) {
  // 獲取觸發(fā)事件的DOM元素
  var nativeEventTarget = getEventTarget(nativeEvent);
  // 初始化事件派發(fā)隊列
  var dispatchQueue = [];
  // 合成事件
  extractEvents$5(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags);
  // 按事件派發(fā)隊列執(zhí)行事件派發(fā)
  processDispatchQueue(dispatchQueue, eventSystemFlags);
}

extractEvents$5中會進行事件合成,放在下面單獨講。

processDispatchQueue會根據(jù)事件階段(冒泡或捕獲)來決定是正序還是倒序遍歷合成事件中的listeners

接下來就比較簡單了。 遍歷listeners執(zhí)行上面的listener

合成事件

在合成事件中,會根據(jù)domEventName來決定使用哪種類型的合成事件。

click為例,當我們點擊頁面的某個元素時,React會根據(jù)原生事件nativeEvent找到觸發(fā)事件的DOM元素和對應的fiber節(jié)點。并以該節(jié)點為孩子節(jié)點往上查找,找到包括該節(jié)點及以上所有的click回調(diào)函數(shù)創(chuàng)建dispatchListener,并添加到listeners數(shù)組中。

// dispatchListener
{
    instance: instance,  // 事件所在的fiber節(jié)點
    listener: listener,  // 事件回調(diào)函數(shù)
    currentTarget: currentTarget  // 事件對應的DOM元素
  }

當向上查找完成后,會基于click類型的合成事件類創(chuàng)建事件

// 創(chuàng)建合成事件實例
var _event = new SyntheticEventCtor(reactName, reactEventType, null, nativeEvent, nativeEventTarget);
// 事件派發(fā)隊列添加事件
dispatchQueue.push({
  event: _event,   // 合成事件實例
  listeners: _listeners  // 同類型事件的集合數(shù)組
});

看下SyntheticEventCtor

// Interface根據(jù)事件類型有所不同
function createSyntheticEvent(Interface) {
  // 合成事件構(gòu)造函數(shù)
  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根據(jù)不同事件類型寫了對應的屬性接口,這里基于接口將原生事件上的屬性clone到構(gòu)造函數(shù)中
    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通過代碼定義好的類型事件的接口和原生事件創(chuàng)建相應的合成事件實例,并重寫了preventDefaultstopPropagation方法。

這樣,同類型的事件會復用同一個合成事件實例對象,節(jié)省了單獨為每一個事件創(chuàng)建事件實例對象的開銷,這就是事件的合成。

捕獲和冒泡

事件派發(fā)分為兩個階段執(zhí)行, 捕獲階段和冒泡階段。

在上面事件合成中講過,React會根據(jù)事件觸發(fā)的fiber節(jié)點向上查找,將上面的同類型事件添加到隊列中,這樣天然就有了一個冒泡的順序,從最底層向上冒泡。如果倒序過來遍歷就是捕獲的順序。

所以,React實現(xiàn)冒泡和捕獲就很簡單了,只需要根據(jù)事件派發(fā)的階段,判斷是冒泡階段還是捕獲階段來決定是正序遍歷listeners還是倒序遍歷就行了。

總結(jié)

說是講React的合成事件,實際上講了React的事件系統(tǒng)。做下總結(jié):

React的事件系統(tǒng)分為幾個部分:

1.事件注冊;

2.事件監(jiān)聽;

3.事件合成;

4.事件派發(fā); 事件系統(tǒng)流程:

  • React代碼執(zhí)行時,內(nèi)部會自動執(zhí)行事件的注冊;
  • 第一次渲染,創(chuàng)建fiberRoot時,會進行事件的監(jiān)聽,所有的事件通過addEventListener委托在id=root的DOM元素上進行監(jiān)聽;
  • 在我們觸發(fā)事件時,會進行事件合成,同類型事件復用一個合成事件類實例對象;
  • 最后進行事件的派發(fā),執(zhí)行我們代碼中的事件回調(diào)函數(shù);

到此這篇關于深入分析React源碼中的合成事件的文章就介紹到這了,更多相關React合成事件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • React源碼分析之useCallback與useMemo及useContext詳解

    React源碼分析之useCallback與useMemo及useContext詳解

    這篇文章主要介紹了React useCallback與useMemo及useContext源碼分析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧
    2022-11-11
  • React關于antd table中select的設值更新問題

    React關于antd table中select的設值更新問題

    這篇文章主要介紹了React關于antd table中select的設值更新問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • react實現(xiàn)移動端下拉菜單的示例代碼

    react實現(xiàn)移動端下拉菜單的示例代碼

    這篇文章主要介紹了react實現(xiàn)移動端下拉菜單的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-01-01
  • React錯誤邊界Error Boundaries

    React錯誤邊界Error Boundaries

    錯誤邊界是一種React組件,這種組件可以捕獲發(fā)生在其子組件樹任何位置的JavaScript錯誤,并打印這些錯誤,同時展示降級UI,而并不會渲染那些發(fā)生崩潰的子組件樹
    2023-01-01
  • react基于Ant Desgin Upload實現(xiàn)導入導出

    react基于Ant Desgin Upload實現(xiàn)導入導出

    本文主要介紹了react基于Ant Desgin Upload實現(xiàn)導入導出,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2024-01-01
  • useState?解決文本框無法輸入的問題詳解

    useState?解決文本框無法輸入的問題詳解

    這篇文章主要為大家介紹了useState?解決文本框無法輸入的問題詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-03-03
  • React組件學習之Hooks使用

    React組件學習之Hooks使用

    這篇文章主要介紹了React hooks組件通信,在開發(fā)中組件通信是React中的一個重要的知識點,本文通過實例代碼給大家講解react hooks中常用的父子、跨組件通信的方法,需要的朋友可以參考下
    2022-08-08
  • React.js源碼解析setState流程

    React.js源碼解析setState流程

    這篇文章主要為大家介紹了React.js源碼解析setState流程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-01-01
  • 在React中this容易遇到的問題詳解

    在React中this容易遇到的問題詳解

    這篇文章主要介紹了在React中this容易遇到的問題總結(jié),文中有詳細的示例代碼,希望對大家有一定的幫助,需要的朋友可以參考下
    2023-05-05
  • 使用react實現(xiàn)手機號的數(shù)據(jù)同步顯示功能的示例代碼

    使用react實現(xiàn)手機號的數(shù)據(jù)同步顯示功能的示例代碼

    本篇文章主要介紹了使用react實現(xiàn)手機號的數(shù)據(jù)同步顯示功能的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-04-04

最新評論