最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

React 事件系統(tǒng)addTrappedEventListener的使用

 更新時(shí)間:2026年07月13日 08:43:33   作者:YFF菲菲兔  
本文主要介紹了React 事件系統(tǒng)addTrappedEventListener的使用,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

React 合成事件系統(tǒng)最底層 DOM 監(jiān)聽器創(chuàng)建與綁定核心函數(shù)

  • 生成 React 封裝的高優(yōu)先級(jí)事件回調(diào)
  • 自動(dòng)區(qū)分 touch/wheel 事件開啟 passive 優(yōu)化
  • 根據(jù)捕獲 / 冒泡 + 是否 passive 分流調(diào)用對(duì)應(yīng)底層綁定 API
  • 生成解綁句柄,供一次性監(jiān)聽自動(dòng)銷毀

完整調(diào)用鏈路全覽

──────────────────────────────────────────────────────────────────────
 createRoot(container)
──────────────────────────────────────────────────────────────────────
  │
  ▼
 listenToAllSupportedEvents(container)
  │
  ├── (container._reactListening = true)
  │
  ├── allNativeEvents.forEach('click'):
  │   ├── 'click' ? nonDelegatedEvents
  │   │   └── listenToNativeEvent('click', false, container)
  │   │       └── addTrappedEventListener(container, 'click', 0, false)
  │   │            ├── createEventListenerWrapperWithPriority(container, 'click', 0)
  │   │            │   └── getEventPriority('click') → DiscreteEventPriority
  │   │            │   └── dispatchDiscreteEvent.bind(null, 'click', 0, container)
  │   │            ├── passiveBrowserEventsSupported? Yes, but click ∈ [touchstart,touchmove,wheel]? No
  │   │            │   └── isPassiveListener = undefined
  │   │            ├── isCapturePhaseListener? false → addEventBubbleListener
  │   │            │   └── container.addEventListener('click', wrapper, false)
  │   │            └── return listener (unsubscribe)
  │   │
  │   └── listenToNativeEvent('click', true, container)
  │       └── addTrappedEventListener(container, 'click', IS_CAPTURE_PHASE, true)
  │           ├── dispatchDiscreteEvent.bind(null, 'click', IS_CAPTURE_PHASE, container)
  │           ├── isCapturePhaseListener? true → addEventCaptureListener
  │           │   └── container.addEventListener('click', wrapper, true)
  │           └── return listener
  │
  ├── allNativeEvents.forEach('touchstart'):
  │   ├── listenToNativeEvent('touchstart', false, container)
  │   │   └── addTrappedEventListener(container, 'touchstart', 0, false)
  │   │       ├── isPassiveListener = true  (domEventName === 'touchstart')
  │   │       ├── isCapturePhaseListener? false, isPassiveListener? true
  │   │       │   └── addEventBubbleListenerWithPassiveFlag(container, 'touchstart', wrapper, true)
  │   │       │       └── container.addEventListener('touchstart', wrapper, { passive: true })
  │   │       └── return listener
  │   │
  │   └── listenToNativeEvent('touchstart', true, container)
  │       └── addTrappedEventListener(container, 'touchstart', IS_CAPTURE_PHASE, true)
  │           ├── isCapturePhaseListener? true, isPassiveListener? true
  │           │   └── addEventCaptureListenerWithPassiveFlag(container, 'touchstart', wrapper, true)
  │           │       └── container.addEventListener('touchstart', wrapper, { capture: true, passive: true })
  │           └── return listener
  │
  └── ownerDocument = document
      └── listenToNativeEvent('selectionchange', false, document)
          └── addTrappedEventListener(document, 'selectionchange', 0, false)
              ├── getEventPriority('selectionchange') → DiscreteEventPriority
              ├── isPassiveListener? false (non-passive)
              └── document.addEventListener('selectionchange', wrapper, false)
──────────────────────────────────────────────────────────────────────
 mount(<img onLoad={fn} />)
──────────────────────────────────────────────────────────────────────
  │
  └── ReactDOMComponent.finalizeInitialChildren
      └── listenToNonDelegatedEvent('load', imgElement)
          ├── listenerSet = imgElement.__reactEvents || new Set()
          ├── listenerSetKey = "load__bubble"
          ├── Set has "load__bubble"? No
          │
          └── addTrappedEventListener(imgElement, 'load', IS_NON_DELEGATED, false)
              ├── createEventListenerWrapperWithPriority(imgElement, 'load', IS_NON_DELEGATED)
              │   └── getEventPriority('load') → DefaultEventPriority
              │   └── dispatchEvent.bind(null, 'load', IS_NON_DELEGATED, imgElement)
              ├── isPassiveListener = undefined (load ? [touchstart,touchmove,wheel])
              └── imgElement.addEventListener('load', wrapper, false)
              └── listenerSet.add("load__bubble")

源碼解析

步驟 ①:創(chuàng)建優(yōu)先級(jí)包裝器

生成非原生、帶 React 調(diào)度能力的攔截回調(diào):原生 DOM 觸發(fā)事件時(shí),不會(huì)直接執(zhí)行組件 onClick,而是先走這個(gè) wrapper。把原生 DOM 事件與 ``React 內(nèi)部渲染調(diào)度、合成事件機(jī)制解耦,所有底層監(jiān)聽統(tǒng)一使用標(biāo)準(zhǔn)化包裝函數(shù),上層不用關(guān)心調(diào)度細(xì)節(jié)

let listener = createEventListenerWrapperWithPriority(
  targetContainer,
  domEventName,
  eventSystemFlags,
);

步驟 ②:被動(dòng)事件優(yōu)化

瀏覽器支持 passive 配置時(shí),滾動(dòng) / 觸摸類事件強(qiáng)制設(shè)為 passive: true。自動(dòng)做滾動(dòng)性能優(yōu)化,開發(fā)者無需手動(dòng)配置,抹平瀏覽器滾動(dòng)卡頓缺陷

let isPassiveListener = undefined;
if (passiveBrowserEventsSupported) {
  if (
    domEventName === 'touchstart' ||
    domEventName === 'touchmove' ||
    domEventName === 'wheel'
  ) {
    isPassiveListener = true;
  }
}

// 特性檢測(cè):瀏覽器是否支持 { passive: true } 選項(xiàng)
if (canUseDOM) {
  try {
    const options = {};
    Object.defineProperty(options, 'passive', {
      get: function () {
        passiveBrowserEventsSupported = true;  // getter 觸發(fā) → 瀏覽器支持
      },
    });
    window.addEventListener('test', options, options);
    window.removeEventListener('test', options, options);
  } catch (e) {
    passiveBrowserEventsSupported = false;
  }
}

檢測(cè)原理

addEventListener 第三個(gè)參數(shù)可以傳入 { passive: true }。如果瀏覽器支持,options.passive 的 getter 會(huì)被觸發(fā),passiveBrowserEventsSupported 設(shè)為 true。如果瀏覽器不支持此選項(xiàng)(如 IE ≤ 11),catch 塊設(shè)為 false

設(shè)計(jì)意義

Chrome 56+ 將 touchstart/touchmove/wheel 的 document 級(jí)監(jiān)聽器默認(rèn)設(shè)為 { passive: true }(因?yàn)殚_發(fā)者幾乎不會(huì)在 touch 事件上調(diào)用 preventDefault 但必須等待 JS 執(zhí)行判斷是否調(diào)用)。React 19 不把事件綁到 document 而綁到 rootContainer,手動(dòng)模擬這個(gè) passive 行為以獲得相同性能收益

步驟 ③:選擇 addEventListener 重載

根據(jù)捕獲 / 冒泡 + 是否 passive 分流到 4 個(gè)底層綁定工具,統(tǒng)一管理 DOM addEventListener 參數(shù)差異,保存解綁函數(shù),支撐一次性監(jiān)聽自動(dòng)移除,為后續(xù)全局卸載根容器事件提供基礎(chǔ)能力

if (isCapturePhaseListener) {
  // 捕獲階段
  if (isPassiveListener !== undefined) {
    unsubscribeListener = addEventCaptureListenerWithPassiveFlag(
      targetContainer, domEventName, listener, isPassiveListener,
    );

  } else {
    unsubscribeListener = addEventCaptureListener(
      targetContainer, domEventName, listener,
    );
  }
} else {
  // 冒泡階段
  if (isPassiveListener !== undefined) {
    unsubscribeListener = addEventBubbleListenerWithPassiveFlag(
      targetContainer, domEventName, listener, isPassiveListener,
    );
  } else {
    unsubscribeListener = addEventBubbleListener(
      targetContainer, domEventName, listener,
    );
  }
}

export function addEventBubbleListener(
  target,
  eventType,
  listener,
) {
  target.addEventListener(eventType, listener, false);
  return listener;
}

export function addEventCaptureListener(
  target,
  eventType,
  listener,
) {
  target.addEventListener(eventType, listener, true);
  return listener;
}

export function addEventCaptureListenerWithPassiveFlag(
  target,
  eventType,
  listener,
  passive,
) {
  target.addEventListener(eventType, listener, {
    capture: true,
    passive,
  });
  return listener;
}

export function addEventBubbleListenerWithPassiveFlag(
  target,
  eventType,
  listener,
  passive,
) {
  target.addEventListener(eventType, listener, {
    passive,
  });
  return listener;
}

四種組合

  • 捕獲 + passive(touch/wheel)
  • 捕獲 + 無 passive(普通事件如 clickCapture)
  • 冒泡 + passive(touch/wheel 常規(guī)監(jiān)聽)
  • 冒泡 + 無 passive(常規(guī) click/input 等)
isCapturePhaseListenerisPassiveListener調(diào)用的函數(shù)DOM 參數(shù)
truetrueaddEventCaptureListenerWithPassiveFlag{ capture: true, passive: true }
trueundefinedaddEventCaptureListenertrue
falsetrueaddEventBubbleListenerWithPassiveFlag{ passive: true }
falseundefinedaddEventBubbleListenerfalse

輔助函數(shù)

創(chuàng)建優(yōu)先級(jí)事件監(jiān)聽器:createEventListenerWrapperWithPriority

export function createEventListenerWrapperWithPriority(
  targetContainer,
  domEventName,
  eventSystemFlags,
) {
  const eventPriority = getEventPriority(domEventName);
  let listenerWrapper;
  switch (eventPriority) {
    case DiscreteEventPriority:
      listenerWrapper = dispatchDiscreteEvent;
      break;
    case ContinuousEventPriority:
      listenerWrapper = dispatchContinuousEvent;
      break;
    case DefaultEventPriority:
    default:
      listenerWrapper = dispatchEvent;
      break;
  }
  return listenerWrapper.bind(
    null,
    domEventName,
    eventSystemFlags,
    targetContainer,
  );
}

作用

  • 調(diào)用 getEventPriority 根據(jù)事件名稱區(qū)分事件優(yōu)先級(jí),React 并發(fā)渲染調(diào)度的核心依據(jù)
    • DiscreteEventPriority:離散高優(yōu)事件(click、keydown、input、focus 等,用戶即時(shí)交互,需要同步響應(yīng),可打斷渲染)
    • ContinuousEventPriority:連續(xù)高頻事件(mousemove、scroll、touchmove 等,持續(xù)觸發(fā),低打斷權(quán)重)
    • DefaultEventPriority:默認(rèn)普通優(yōu)先級(jí)(其余事件)
  • 按事件類型分流調(diào)度邏輯,實(shí)現(xiàn) React 并發(fā)渲染的優(yōu)先級(jí)機(jī)制,不同交互行為擁有不同渲染搶占能力
    • 離散高優(yōu)事件:使用 dispatchDiscreteEvent。標(biāo)記離散更新、同步刷新、允許中斷后臺(tái)渲染,保證點(diǎn)擊、輸入即時(shí)響應(yīng)
    • 連續(xù)高頻事件:使用 dispatchContinuousEvent。走連續(xù)調(diào)度,合并多次更新,避免高頻觸發(fā)造成頁(yè)面卡頓
    • 默認(rèn) / 其他事件:兜底 dispatchEvent,標(biāo)準(zhǔn)合成事件流程
  • bind 預(yù)綁定固定參數(shù),返回最終監(jiān)聽回調(diào):把靜態(tài)不變的配置(事件名、捕獲標(biāo)記、根容器)提前固化,不用每次事件觸發(fā)再傳遞 / 查找,減少運(yùn)行時(shí)計(jì)算開銷;同時(shí)統(tǒng)一分發(fā)函數(shù)入?yún)⒁?guī)范,下層分發(fā)邏輯無需額外從 DOM 讀取信息
    • 預(yù)固化三個(gè)前置參數(shù):domEventName、eventSystemFlags、targetContainer
    • 當(dāng)瀏覽器觸發(fā)事件執(zhí)行回調(diào)時(shí),原生 Event 對(duì)象會(huì)作為第四個(gè)參數(shù)傳入分發(fā)函數(shù)

事件優(yōu)先級(jí)映射(getEventPriority)

優(yōu)先級(jí)代表事件包裝器
DiscreteEventPriorityclick, keydown, focusin, input, mousedown, touchstart, submit, toggle, beforetoggle, cancel, close, paste, copy, cut, contextmenu, auxclick, dblclick, dragend, dragstart, drop, invalid, keypress, keyup, mouseup, pause, play, pointercancel, pointerdown, pointerup, ratechange, reset, seeked, touchcancel, touchend, volumechange, change, selectionchange, beforeblur, afterblur, blur, focus, hashchange, popstate, select, selectstart, beforeinput, fullscreenchange, fullscreenerrordispatchDiscreteEvent
ContinuousEventPrioritymousemove, mouseout, mouseover, pointermove, pointerout, pointerover, drag, dragenter, dragexit, dragleave, dragover, resize, scroll, touchmove, wheel, mouseenter, mouseleave, pointerenter, pointerleavedispatchContinuousEvent
DefaultEventPriorityload, error, animationend, transitionend, message(特定調(diào)度優(yōu)先級(jí))等不匹配上述分類的事件dispatchEvent

離散事件的調(diào)度包裝器:dispatchDiscreteEvent

  • 臨時(shí)提升渲染優(yōu)先級(jí),保障交互即時(shí)性:針對(duì) click/input/keydown 等離散用戶交互,把事件內(nèi)所有狀態(tài)更新強(qiáng)制設(shè)為最高優(yōu)先級(jí),能夠打斷后臺(tái)正在執(zhí)行的低優(yōu)先級(jí)渲染,解決點(diǎn)擊延遲、輸入卡頓問題,是 React 并發(fā)渲染交互流暢性的核心保障
  • 隔離 startTransition 低優(yōu)上下文:強(qiáng)制清空全局 Transition 標(biāo)記,確保離散交互不受 startTransition 降級(jí)邏輯影響,緊急操作永遠(yuǎn)優(yōu)先渲染
  • 上下文壓棧 + 自動(dòng)恢復(fù),無全局污染:采用先緩存現(xiàn)場(chǎng) → 修改全局環(huán)境 → 執(zhí)行事件邏輯 → 無論成敗強(qiáng)制還原的范式,不會(huì)篡改外部代碼的優(yōu)先級(jí)、Transition 環(huán)境,多個(gè)事件嵌套、并發(fā)執(zhí)行時(shí)上下文互不干擾
function dispatchDiscreteEvent(
  domEventName,
  eventSystemFlags,
  container,
  nativeEvent,
) {
  const prevTransition = ReactSharedInternals.T;
  ReactSharedInternals.T = null;
  const previousPriority = getCurrentUpdatePriority();
  try {
    setCurrentUpdatePriority(DiscreteEventPriority);
    dispatchEvent(domEventName, eventSystemFlags, container, nativeEvent);
  } finally {
    setCurrentUpdatePriority(previousPriority);
    ReactSharedInternals.T = prevTransition;
  }
}

設(shè)計(jì)意義

即使當(dāng)前代碼處在 startTransition 內(nèi)部,點(diǎn)擊 / 輸入這類緊急交互也要走高優(yōu)渲染,不能被降級(jí)延遲,事件執(zhí)行過程中產(chǎn)生的所有 setState 更新,都會(huì)標(biāo)記為高優(yōu),可以中斷正在進(jìn)行的后臺(tái)渲染,保證交互即時(shí)響應(yīng)

連續(xù)事件的調(diào)度包裝器:dispatchContinuousEvent

  • 設(shè)置連續(xù)事件專屬調(diào)度優(yōu)先級(jí),平衡流暢性與性能針對(duì)滾動(dòng)、鼠標(biāo)移動(dòng)、拖拽、滾輪等每秒多次觸發(fā)的高頻事件,使用中等優(yōu)先級(jí):
    • 不會(huì)像離散點(diǎn)擊那樣強(qiáng)行搶占渲染;
    • 多個(gè)連續(xù)更新會(huì)被調(diào)度器合并,減少重復(fù)渲染開銷;
    • 當(dāng)同時(shí)發(fā)生點(diǎn)擊輸入時(shí),離散事件可以打斷滾動(dòng)渲染,保證用戶主動(dòng)交互優(yōu)先
  • 剝離 startTransition 低優(yōu)上下文:清空全局 Transition 標(biāo)記,滾動(dòng)相關(guān)更新不會(huì)被當(dāng)作延遲渲染任務(wù),避免滾動(dòng)滯后、畫面掉幀
  • 安全隔離全局調(diào)度環(huán)境,無副作用污染:采用緩存現(xiàn)場(chǎng) → 臨時(shí)覆蓋 → 最終強(qiáng)制還原的棧式上下文模式,單次事件執(zhí)行僅局部生效,函數(shù)退出后全局狀態(tài)完全復(fù)原,異常場(chǎng)景也不會(huì)破壞全局調(diào)度規(guī)則
function dispatchContinuousEvent(
  domEventName,
  eventSystemFlags,
  container,
  nativeEvent,
) {
  const prevTransition = ReactSharedInternals.T;
  ReactSharedInternals.T = null;
  const previousPriority = getCurrentUpdatePriority();
  try {
    setCurrentUpdatePriority(ContinuousEventPriority);
    dispatchEvent(domEventName, eventSystemFlags, container, nativeEvent);
  } finally {
    setCurrentUpdatePriority(previousPriority);
    ReactSharedInternals.T = prevTransition;
  }
}

設(shè)計(jì)意義

滾動(dòng)、鼠標(biāo)移動(dòng)這類持續(xù)交互,不降級(jí)為 transition 更新,使用專屬連續(xù)優(yōu)先級(jí)(優(yōu)先級(jí)低于點(diǎn)擊、輸入等離散事件),后臺(tái)渲染任務(wù)遇到離散交互會(huì)被直接搶占中斷,連續(xù)事件之間會(huì)合并調(diào)度,減少重復(fù)渲染,防止高頻事件卡死頁(yè)面。高頻滾動(dòng)事件不搶占主線程,避免短時(shí)間大量渲染阻塞頁(yè)面

事件分發(fā)中樞控制器:dispatchEvent

針對(duì) SSR 水合阻塞場(chǎng)景做分流處理

  • 無未水合 DOM:直接執(zhí)行完整事件流程
  • 高頻連續(xù)事件遇阻塞:暫存隊(duì)列、丟棄本次觸發(fā)
  • 捕獲階段離散交互事件遇阻塞:同步強(qiáng)制水合組件,再執(zhí)行事件
  • 其余普通阻塞場(chǎng)景:直接執(zhí)行事件(不等待水合)
export function dispatchEvent(
  domEventName,
  eventSystemFlags,
  targetContainer,
  nativeEvent,
) {
  // 標(biāo)記 React 合成事件系統(tǒng)是否啟用;關(guān)閉則直接跳過所有事件處理
  if (!_enabled) {
    return;
  }
  // 查找當(dāng)前觸發(fā)事件的 DOM 節(jié)點(diǎn)鏈上,還未完成水合(hydrate)的 DOM 元素;返回對(duì)應(yīng) DOM,無阻塞節(jié)點(diǎn)返回 null
  let blockedOn = findInstanceBlockingEvent(nativeEvent);
  if (blockedOn === null) {
    dispatchEventForPluginEventSystem(
      domEventName,
      eventSystemFlags,
      nativeEvent,
      return_targetInst,  // 事件目標(biāo) DOM 對(duì)應(yīng)的 Fiber 實(shí)例
      targetContainer,
    );
    // 清空當(dāng)前連續(xù)事件緩存隊(duì)列,丟棄過期事件
    clearIfContinuousEvent(domEventName, nativeEvent);
    return;
  }

  if (
    // 連續(xù)事件(scroll/mousemove/touchmove)入隊(duì)
    queueIfContinuousEvent(
      blockedOn,
      domEventName,
      eventSystemFlags,
      targetContainer,
      nativeEvent,
    )
  ) {
    nativeEvent.stopPropagation();
    return;
  }
  clearIfContinuousEvent(domEventName, nativeEvent);

  if (
    eventSystemFlags & IS_CAPTURE_PHASE &&
    // 判斷該離散事件是否必須等待水合完成才能執(zhí)行(click、input、keydown 等用戶強(qiáng)交互)
    isDiscreteEventThatRequiresHydration(domEventName)
  ) {
    while (blockedOn !== null) {
      const fiber = getInstanceFromNode(blockedOn);
      if (fiber !== null) {
        attemptSynchronousHydration(fiber);
      }
      const nextBlockedOn = findInstanceBlockingEvent(nativeEvent);
      if (nextBlockedOn === null) {
        dispatchEventForPluginEventSystem(
          domEventName,
          eventSystemFlags,
          nativeEvent,
          return_targetInst,
          targetContainer,
        );
      }
      if (nextBlockedOn === blockedOn) {
        break;
      }
      blockedOn = nextBlockedOn;
    }
    if (blockedOn !== null) {
      nativeEvent.stopPropagation();
    }
    return;
  }

  dispatchEventForPluginEventSystem(
    domEventName,
    eventSystemFlags,
    nativeEvent,
    null,
    targetContainer,
  );
}

作用

  • 事件系統(tǒng)總開關(guān)攔截:全局關(guān)閉合成事件系統(tǒng)時(shí),直接終止事件分發(fā),不執(zhí)行任何后續(xù)邏輯
  • 調(diào)用 findInstanceBlockingEvent 查找是否存在未水合阻塞 DOM:從原生事件觸發(fā)目標(biāo)向上遍歷 DOM 樹,查找對(duì)應(yīng) Fiber 未完成水合的 DOM 節(jié)點(diǎn)
  • 無任何未水合阻塞節(jié)點(diǎn)(正常流程):直接執(zhí)行事件,傳入目標(biāo) Fiber,清空連續(xù)事件隊(duì)列
    • 調(diào)用核心事件執(zhí)行函數(shù) dispatchEventForPluginEventSystem,傳入目標(biāo) DOM 對(duì)應(yīng)的 Fiber 實(shí)例 return_targetInst,完整執(zhí)行合成事件、遍歷 Fiber、執(zhí)行用戶回調(diào)
    • clearIfContinuousEvent:清空該事件類型的連續(xù)事件緩存隊(duì)列,直接退出
  • 存在阻塞節(jié)點(diǎn),優(yōu)先處理高頻連續(xù)事件:事件入隊(duì)延遲執(zhí)行,停止冒泡,放棄本次分發(fā),不阻塞主線程
    • queueIfContinuousEvent 判斷當(dāng)前是否為 scroll/mousemove/touchmove 等連續(xù)高頻事件
      • 是連續(xù)事件:將本次事件存入延遲隊(duì)列,等待水合完成后統(tǒng)一批量執(zhí)行;調(diào)用 nativeEvent.stopPropagation 停止原生事件冒泡,不繼續(xù)向下分發(fā),直接退出
      • 非連續(xù)事件:執(zhí)行 clearIfContinuousEvent 清空歷史緩存
  • 捕獲階段 + 存在阻塞需要強(qiáng)制水合的離散交互事件(click/input 等):同步強(qiáng)制水合所有阻塞組件,水合完成后執(zhí)行事件,若水合失敗則停止冒泡,保證即時(shí)響應(yīng)
    • 根據(jù)阻塞 DOM 拿到對(duì)應(yīng) Fiber
    • attemptSynchronousHydration:同步阻塞主線程,強(qiáng)制完成該組件水合
    • 重新查找阻塞節(jié)點(diǎn) nextBlockedOn
    • 如果水合后無阻塞節(jié)點(diǎn),直接執(zhí)行完整事件分發(fā)
    • 如果前后阻塞節(jié)點(diǎn)不變,代表無法完成水合,跳出循環(huán)
    • 更新 blockedOn,循環(huán)處理下一個(gè)阻塞 DOM
    • 循環(huán)結(jié)束后,若仍存在未水合節(jié)點(diǎn),停止原生事件冒泡,直接退出
  • 兜底分支:普通冒泡離散事件,存在阻塞但無需同步水合:不阻塞主線程,直接執(zhí)行事件,動(dòng)態(tài)查找 Fiber 兼容半水合狀態(tài)

設(shè)計(jì)意義

  • SSR/SSG 頁(yè)面:HTML 先渲染到頁(yè)面,JS 加載完成后才會(huì)執(zhí)行水合;此時(shí)點(diǎn)擊 DOM,對(duì)應(yīng)組件還未掛載 Fiber,無法讀取事件回調(diào),需要特殊兼容
  • 丟棄過期高頻過渡事件:懸浮、焦點(diǎn)、拖拽類事件觸發(fā)頻率極高,水合階段積壓大量事件會(huì)造成水合完成后主線程卡頓;執(zhí)行有效事件后清空緩存,減少后續(xù)批量執(zhí)行開銷,提升 SSR 頁(yè)面交互流暢度
  • 高頻滾動(dòng)類事件不需要同步阻塞等待水合,入隊(duì)延遲執(zhí)行,避免主線程卡頓;同時(shí)停止冒泡,防止原生事件繼續(xù)擴(kuò)散
  • 捕獲階段的用戶點(diǎn)擊、輸入是強(qiáng)即時(shí)交互,不能延遲;因此強(qiáng)制同步水合組件,保證事件回調(diào)可以正常執(zhí)行,解決 SSR 頁(yè)面點(diǎn)擊失效問題
  • 普通冒泡離散事件不阻塞主線程同步水合,直接嘗試執(zhí)行事件;框架內(nèi)部動(dòng)態(tài)兼容半水合狀態(tài),平衡交互響應(yīng)速度與主線程阻塞開銷

查找未水合阻塞 DOM:findInstanceBlockingEvent

  • 專門識(shí)別三類會(huì)導(dǎo)致事件失效的特殊容器:脫水根、Suspense、Activity
  • 判定是否存在水合阻塞容器,存在則返回阻塞 DOM;
  • 緩存事件目標(biāo)對(duì)應(yīng)的有效 Fiber 到全局變量,供事件插件系統(tǒng)復(fù)用,避免重復(fù)查找 DOM-Fiber 映射
  • 當(dāng)事件觸發(fā)節(jié)點(diǎn)自身未掛載,僅上層祖先掛載時(shí),清空 Fiber 實(shí)例,避免事件系統(tǒng)讀取未就緒組件的回調(diào),防止報(bào)錯(cuò)
export function findInstanceBlockingEvent(
  nativeEvent,
) {
  const nativeEventTarget = getEventTarget(nativeEvent);
  return findInstanceBlockingTarget(nativeEventTarget);
}

function getEventTarget(nativeEvent) {
  let target = nativeEvent.target || nativeEvent.srcElement || window;

  if (target.correspondingUseElement) {
    target = target.correspondingUseElement;
  }
  return target.nodeType === TEXT_NODE ? target.parentNode : target;
}

export let return_targetInst = null;

export function findInstanceBlockingTarget(
  targetNode,
) {

  return_targetInst = null;
  // 從 DOM 向上找最近綁定 Fiber 的實(shí)例
  let targetInst = getClosestInstanceFromNode(targetNode);

  if (targetInst !== null) {
    // 從當(dāng)前 Fiber 向上找最近已掛載的 Fiber
    const nearestMounted = getNearestMountedFiber(targetInst);
    if (nearestMounted === null) {
      targetInst = null;
    } else {
      const tag = nearestMounted.tag;
      // Suspense 組件,子樹延遲加載,內(nèi)部 DOM 已渲染但未激活
      if (tag === SuspenseComponent) {
        const instance = getSuspenseInstanceFromFiber(nearestMounted);
        if (instance !== null) {
          return instance;
        }
        targetInst = null;
      // 離屏緩存組件(React 內(nèi)部),DOM 存在但組件未激活
      } else if (tag === ActivityComponent) {
        const instance = getActivityInstanceFromFiber(nearestMounted);
        if (instance !== null) {
          return instance;
        }
        targetInst = null;
      // 應(yīng)用根 Fiber,對(duì)應(yīng) #root 容器
      } else if (tag === HostRoot) {
        const root = nearestMounted.stateNode;
        if (isRootDehydrated(root)) {
          return getContainerFromFiber(nearestMounted);
        }
        targetInst = null;
      } else if (nearestMounted !== targetInst) {
        targetInst = null;
      }
    }
  }
  return_targetInst = targetInst;
  return null;
}

作用

  • 獲取事件真實(shí)觸發(fā) DOM:統(tǒng)一抹平瀏覽器事件 target 相關(guān) API 差異,正確取出事件真正觸發(fā)的 DOM 節(jié)點(diǎn),拿到事件源頭 DOM,作為向上遍歷查找阻塞節(jié)點(diǎn)的起點(diǎn)
  • 轉(zhuǎn)發(fā)給核心查找函數(shù) findInstanceBlockingTarget
    • 調(diào)用 getClosestInstanceFromNode 獲取當(dāng)前 DOM 綁定的最近 Fiber:從傳入的 DOM 節(jié)點(diǎn)向上遍歷,拿到綁定在 DOM 上的 Fiber 實(shí)例
    • 存在 Fiber 實(shí)例,調(diào)用 getNearestMountedFiber,從當(dāng)前 Fiber 向上遍歷 Fiber 樹,找到第一個(gè)已完成掛載的祖先 Fiber
    • 無任何已掛載祖先 Fiber:整條組件樹都未掛載,不存在可響應(yīng)事件的 Fiber,清空 targetInst,后續(xù)直接返回 null
    • 最近掛載節(jié)點(diǎn)是 Suspense
      • getSuspenseInstanceFromFiber 獲取 Suspense 對(duì)應(yīng)的 DOM 容器
      • 容器存在 → 直接返回該 DOM,代表被 Suspense 阻塞水合
      • 無容器則清空 targetInst,視為無阻塞
    • 最近掛載節(jié)點(diǎn)是 Activity 離屏組件:DOM 保留在頁(yè)面但組件處于凍結(jié) / 未激活狀態(tài),事件無法正常響應(yīng),找到對(duì)應(yīng) DOM 容器直接返回,標(biāo)記為阻塞節(jié)點(diǎn)
    • 最近掛載節(jié)點(diǎn)是應(yīng)用根 HostRoot,判斷根容器是否處于脫水未水合狀態(tài)(SSR 剛加載完 HTML,JS 還沒完成水合)
      • 脫水狀態(tài):返回根容器 DOM,代表全局水合阻塞
      • 已完成水合:清空 targetInst
    • 最近掛載 Fiber 不是當(dāng)前事件節(jié)點(diǎn)自身:當(dāng)前觸發(fā)事件的 Fiber 自身未掛載,僅上層祖先掛載;組件未就緒,不能作為有效事件目標(biāo),清空 targetInst
    • 把有效 Fiber 存入全局變量供上層使用:走完所有阻塞判斷后,若 targetInst 不為 null,代表當(dāng)前 DOM 對(duì)應(yīng)的組件已正常掛載、無阻塞;存入全局變量,后續(xù)插件事件系統(tǒng)可以直接拿到事件目標(biāo) Fiber,快速收集事件處理器
    • 函數(shù)統(tǒng)一返回 null:只有上面 Suspense/Activity/ 脫水根三個(gè)分支會(huì)提前 return DOM 阻塞節(jié)點(diǎn);其余所有正常、無阻塞場(chǎng)景統(tǒng)一返回 null

清理緩存的過渡類事件:clearIfContinuousEvent

清理 SSR 半水合階段積壓的過渡類事件緩存,最終只執(zhí)行最新一次交互事件,中間滑動(dòng)產(chǎn)生的過渡事件全部作廢。當(dāng)頁(yè)面處于半水合狀態(tài)時(shí),queueIfContinuousEvent 會(huì)把頻繁觸發(fā)的懸浮、焦點(diǎn)、拖拽、指針事件存入全局隊(duì)列延遲執(zhí)行;一旦當(dāng)前事件可以正常分發(fā)(組件已水合、無阻塞),調(diào)用本函數(shù)清空對(duì)應(yīng)緩存,避免過期事件后續(xù)補(bǔ)發(fā)造成狀態(tài)錯(cuò)亂

  • 本次焦點(diǎn)事件正常分發(fā)執(zhí)行,清空之前緩存的焦點(diǎn)隊(duì)列,丟棄未執(zhí)行的舊焦點(diǎn)事件
  • 清空拖拽事件緩存,過期拖拽事件不再補(bǔ)發(fā)
  • 清空鼠標(biāo)懸浮緩存
  • 只刪除當(dāng)前觸點(diǎn)對(duì)應(yīng)的緩存事件,不影響其他觸點(diǎn)隊(duì)列(指針設(shè)備支持多觸點(diǎn),用 pointerId 區(qū)分不同手指 / 觸控筆)
  • 按觸點(diǎn) ID 清除對(duì)應(yīng)指針捕獲緩存事件
export function clearIfContinuousEvent(
  domEventName,
  nativeEvent,
) {
  switch (domEventName) {
    case 'focusin':
    case 'focusout':
      // 緩存 focusin /focusout 焦點(diǎn)過渡事件
      queuedFocus = null;
      break;
    case 'dragenter':
    case 'dragleave':
      // 緩存 dragenter /dragleave 拖拽進(jìn)出事件
      queuedDrag = null;
      break;
    case 'mouseover':
    case 'mouseout':
      // 緩存 mouseover /mouseout 鼠標(biāo)懸浮進(jìn)出事件
      queuedMouse = null;
      break;
    case 'pointerover':
    case 'pointerout': {
      // 緩存 pointerover/pointerout 指針懸浮事件
      const pointerId = nativeEvent.pointerId;
      queuedPointers.delete(pointerId);
      break;
    }
    case 'gotpointercapture':
    case 'lostpointercapture': {
      // 緩存 gotpointercapture/lostpointercapture 指針捕獲事件
      const pointerId = nativeEvent.pointerId;
      queuedPointerCaptures.delete(pointerId);
      break;
    }
  }
}

緩存高頻過渡類事件:queueIfContinuousEvent

focusin / dragenter / mouseover / pointerover / gotpointercapture 均為高頻、成對(duì)切換、瞬時(shí)狀態(tài)過渡事件,短時(shí)間會(huì)大量連續(xù)觸發(fā);SSR 半水合階段如果立刻執(zhí)行會(huì)阻塞主線程,且大量中間事件無業(yè)務(wù)價(jià)值,只需要保留最新一次回放

  • 匹配到可緩存的連續(xù)過渡事件:存入緩存并返回 true;上層執(zhí)行 nativeEvent.stopPropagation(),終止本次事件分發(fā)
  • 不匹配任何 case(click/input/scroll/wheel 等):返回 false,上層繼續(xù)走正常阻塞事件分發(fā)邏輯
export function queueIfContinuousEvent(
  blockedOn,
  domEventName,
  eventSystemFlags,
  targetContainer,
  nativeEvent,
) {
  switch (domEventName) {
    case 'focusin': {
      const focusEvent = nativeEvent;
      queuedFocus = accumulateOrCreateContinuousQueuedReplayableEvent(
        queuedFocus,
        blockedOn,
        domEventName,
        eventSystemFlags,
        targetContainer,
        focusEvent,
      );
      return true;
    }
    case 'dragenter': {
      const dragEvent = nativeEvent;
      queuedDrag = accumulateOrCreateContinuousQueuedReplayableEvent(
        queuedDrag,
        blockedOn,
        domEventName,
        eventSystemFlags,
        targetContainer,
        dragEvent,
      );
      return true;
    }
    case 'mouseover': {
      const mouseEvent = nativeEvent;
      queuedMouse = accumulateOrCreateContinuousQueuedReplayableEvent(
        queuedMouse,
        blockedOn,
        domEventName,
        eventSystemFlags,
        targetContainer,
        mouseEvent,
      );
      return true;
    }
    case 'pointerover': {
      const pointerEvent = nativeEvent;
      const pointerId = pointerEvent.pointerId;
      queuedPointers.set(
        pointerId,
        accumulateOrCreateContinuousQueuedReplayableEvent(
          queuedPointers.get(pointerId) || null,
          blockedOn,
          domEventName,
          eventSystemFlags,
          targetContainer,
          pointerEvent,
        ),
      );
      return true;
    }
    case 'gotpointercapture': {
      const pointerEvent = nativeEvent;
      const pointerId = pointerEvent.pointerId;
      queuedPointerCaptures.set(
        pointerId,
        accumulateOrCreateContinuousQueuedReplayableEvent(
          queuedPointerCaptures.get(pointerId) || null,
          blockedOn,
          domEventName,
          eventSystemFlags,
          targetContainer,
          pointerEvent,
        ),
      );
      return true;
    }
  }
  return false;
}

連續(xù)過渡事件緩存工具:accumulateOrCreateContinuousQueuedReplayableEvent

  • 無舊緩存 / 原生事件對(duì)象不同:調(diào)用 createQueuedReplayableEvent 新建一份可回放事件快照,統(tǒng)一封裝回放所需全部上下文,后續(xù)水合完成后遍歷緩存、執(zhí)行事件分發(fā)時(shí),僅依賴 QueuedReplayableEvent 即可完整復(fù)現(xiàn)當(dāng)時(shí)事件環(huán)境,無需額外保存臨時(shí)變量,并對(duì)阻塞 Fiber 提升水合優(yōu)先級(jí),調(diào)度器會(huì)優(yōu)先執(zhí)行這個(gè)組件的水合邏輯,盡快解除阻塞,減少事件持續(xù)緩存積壓,避免大量過渡事件長(zhǎng)期駐留內(nèi)存
    • 保存阻塞節(jié)點(diǎn)、事件名稱、初始 flags
    • targetContainers 數(shù)組存入當(dāng)前根容器
    • 綁定原生事件對(duì)象作為唯一標(biāo)識(shí)
    • 舊緩存會(huì)被本次新快照覆蓋,舊快照失去引用,等待 GC 回收,只保留最新一次交互狀態(tài)
    • 從阻塞 DOM 取出對(duì)應(yīng) Fiber
    • 標(biāo)記該 Fiber 為高優(yōu)連續(xù)水合任務(wù)
    • 返回新建快照,覆蓋全局緩存
  • 已經(jīng)存在同一份原生事件緩存:合并標(biāo)記、收集多根容器,復(fù)用舊快照(高頻過渡事件短時(shí)間大量觸發(fā),如果每次都新建快照會(huì)頻繁 GC),不重復(fù)創(chuàng)建,減少內(nèi)存對(duì)象創(chuàng)建,降低 GC 開銷
    • 把本次監(jiān)聽的捕獲 / 非委托標(biāo)記合并到舊快照,回放時(shí)同時(shí)攜帶兩套標(biāo)記
    • 快照內(nèi) targetContainers 是數(shù)組,存儲(chǔ)所有觸發(fā)該事件的根容器;如果當(dāng)前根容器不在數(shù)組中,追加進(jìn)去;保證回放時(shí)能在所有關(guān)聯(lián) root 分發(fā)事件
    • 返回原有合并后的快照
function accumulateOrCreateContinuousQueuedReplayableEvent(
  existingQueuedEvent,
  blockedOn,
  domEventName,
  eventSystemFlags,
  targetContainer,
  nativeEvent,
) {
  if (
    existingQueuedEvent === null ||
    existingQueuedEvent.nativeEvent !== nativeEvent
  ) {
    const queuedEvent = createQueuedReplayableEvent(
      blockedOn,
      domEventName,
      eventSystemFlags,
      targetContainer,
      nativeEvent,
    );
    if (blockedOn !== null) {
      const fiber = getInstanceFromNode(blockedOn);
      if (fiber !== null) {
        // 給被阻塞的 Fiber 打上連續(xù)事件水合優(yōu)先級(jí),調(diào)度器優(yōu)先處理該組件水合,盡快解除阻塞
        attemptContinuousHydration(fiber);
      }
    }
    return queuedEvent;
  }
  existingQueuedEvent.eventSystemFlags |= eventSystemFlags;
  const targetContainers = existingQueuedEvent.targetContainers;
  if (
    targetContainer !== null &&
    targetContainers.indexOf(targetContainer) === -1
  ) {
    targetContainers.push(targetContainer);
  }
  return existingQueuedEvent;
}

function createQueuedReplayableEvent(
  blockedOn,
  domEventName,
  eventSystemFlags,
  targetContainer,
  nativeEvent,
) {
  return {
    blockedOn,
    domEventName,
    eventSystemFlags,
    nativeEvent,
    targetContainers: [targetContainer],
  };
}

事件邊界校正器 + 批量更新入口:dispatchEventForPluginEventSystem

  • 對(duì)常規(guī)委托事件,修正事件向上遍歷的頂層邊界 ancestorInst,保證事件冒泡僅在當(dāng)前 React 根容器內(nèi)部傳播,不跨 root、不跨 Portal 污染其他應(yīng)用
    • 單根應(yīng)用普通 DOM:向上遍歷 Fiber 命中當(dāng)前 HostRoot,break 循環(huán),ancestorInst 停在根 Fiber,事件冒泡范圍整個(gè) root 內(nèi)部
    • Portal 內(nèi)部觸發(fā)事件
      • Portal 容器不屬于當(dāng)前 root,內(nèi)層循環(huán)向上查找父 Fiber
      • 若 Portal 掛載到外部 DOM,直接 return,事件不會(huì)冒泡到 Portal 外部組件
      • 若 Portal 容器 DOM 能找到內(nèi)部托管組件,重置起點(diǎn)重新查找根邊界
    • 多 Root 并行應(yīng)用:每個(gè) root 獨(dú)立綁定事件,邊界校正會(huì)限制冒泡僅在當(dāng)前 root 內(nèi),A 根事件不會(huì)觸發(fā) B 根組件回調(diào),隔離多應(yīng)用
  • 把插件事件執(zhí)行邏輯包裹進(jìn)批量更新,統(tǒng)一觸發(fā)批量渲染
export function dispatchEventForPluginEventSystem(
  domEventName,
  eventSystemFlags,
  nativeEvent,
  targetInst,
  targetContainer,
) {
  let ancestorInst = targetInst;
  if (
    // 當(dāng)前 DOM 是 React 托管組件,不是第三方外部 DOM
    (eventSystemFlags & IS_EVENT_HANDLE_NON_MANAGED_NODE) === 0 &&
    // 當(dāng)前事件支持冒泡委托(click/input 等常規(guī)事件,不是 scroll/focus 這類不冒泡事件)
    (eventSystemFlags & IS_NON_DELEGATED) === 0
  ) {
    const targetContainerNode = targetContainer;

    if (targetInst !== null) {
      let node = targetInst;

      mainLoop: while (true) {
        if (node === null) {
          return;
        }
        const nodeTag = node.tag;
        if (nodeTag === HostRoot || nodeTag === HostPortal) {
          let container = node.stateNode.containerInfo;
          // 判斷 DOM 容器是否為當(dāng)前事件綁定的根容器
          if (isMatchingRootContainer(container, targetContainerNode)) {
            break;
          }
          if (nodeTag === HostPortal) {
            let grandNode = node.return;
            while (grandNode !== null) {
              const grandTag = grandNode.tag;
              if (grandTag === HostRoot || grandTag === HostPortal) {
                const grandContainer = grandNode.stateNode.containerInfo;
                if (
                  isMatchingRootContainer(grandContainer, targetContainerNode)
                ) {
                  return;
                }
              }
              grandNode = grandNode.return;
            }
          }
 
          while (container !== null) {
            const parentNode = getClosestInstanceFromNode(container);
            if (parentNode === null) {
              return;
            }
            const parentTag = parentNode.tag;
            if (
              parentTag === HostComponent ||
              parentTag === HostText ||
              parentTag === HostHoistable ||
              parentTag === HostSingleton
            ) {
              node = ancestorInst = parentNode;
              continue mainLoop;
            }
            container = container.parentNode;
          }
        }
        node = node.return;
      }
    }
  }
  // 批量更新,內(nèi)部回調(diào)所有 setState 會(huì)合并批量渲染,減少重渲染
  batchedUpdates(() =>
    // 事件插件體系真正執(zhí)行邏輯,生成合成事件、遍歷 Fiber 執(zhí)行 onXxx 回調(diào)
    dispatchEventsForPlugins(
      domEventName,
      eventSystemFlags,
      nativeEvent,
      ancestorInst,
      targetContainer,
    ),
  );
}

作用

  • 初始化事件向上冒泡遍歷 Fiber 樹的終止邊界節(jié)點(diǎn) ancestorInst
  • 僅對(duì)托管 + 可委托事件做邊界裁剪:當(dāng)前 DOM 是 React 托管組件,不是第三方外部 DOM 且當(dāng)前事件支持冒泡委托(click/input 等常規(guī)事件,不是 scroll/focus 這類不冒泡事件)
  • 僅目標(biāo)存在 Fiber 才執(zhí)行邊界遍歷
  • 向上遍歷 Fiber 樹,尋找當(dāng)前根容器邊界:從當(dāng)前 Fiber 持續(xù)向上 node.return 遍歷父 Fiber
  • 遍歷到 HostRoot / HostPortal(根 / 傳送門)
    • 取出 Root/Portal 對(duì)應(yīng)的真實(shí) DOM 容器
    • 如果容器等于當(dāng)前事件綁定的根容器 → 命中邊界,跳出循環(huán),當(dāng)前 node 作為冒泡終點(diǎn)
    • 不匹配則進(jìn)入 Portal 特殊處理邏輯
  • Portal 特殊處理:跨 Portal 不能冒泡到外部根,Portal 節(jié)點(diǎn)渲染到外部 DOM,邏輯上父級(jí)不在當(dāng)前 root 內(nèi)部,隔離傳送門事件作用域
    • 向上查找 Portal 的父 Fiber 鏈
    • 若找到當(dāng)前根容器,說明 Portal 渲染范圍在外部,直接 return 終止整個(gè)函數(shù)
  • 向上回溯 DOM 容器,重新定位容器內(nèi) Fiber(Portal 跨 DOM 場(chǎng)景修復(fù)):Portal 渲染到外部 DOM,F(xiàn)iber 樹和 DOM 樹層級(jí)不一致
    • 從 Portal 容器 DOM 向上遍歷原生 DOM
    • 找到第一個(gè) React 托管 DOM 組件 Fiber
    • 重置遍歷起點(diǎn) node 和冒泡邊界 ancestorInst,重啟主循環(huán)重新查找根邊界
    • 找不到托管 Fiber 直接 return,終止事件分發(fā)
  • 未命中 Root/Portal,繼續(xù)向上遍歷父 Fiber
  • 邊界修正完成,進(jìn)入批量執(zhí)行完整合成事件流程

設(shè)計(jì)意義

  • 多根、Portal 事件隔離,修復(fù)原生 DOM 冒泡穿透問題:瀏覽器原生 DOM 冒泡不受 React 根容器、Portal 邊界限制,會(huì)跨 DOM 樹傳播;本函數(shù)通過校正 ancestorInst 限定事件遍歷上限,實(shí)現(xiàn)多 #root 應(yīng)用事件互相隔離和 Portal 內(nèi)部事件不冒泡到外部父組件。處理 Portal 渲染到外部 DOM 導(dǎo)致 Fiber、DOM 層級(jí)不匹配的問題,抹平 DOM 樹與 Fiber 樹層級(jí)不一致的行為差異
  • 統(tǒng)一批量更新包裝:所有用戶事件回調(diào)統(tǒng)一包裹在 batchedUpdates,自動(dòng)合并多次狀態(tài)更新,避免高頻事件重復(fù)渲染,保障頁(yè)面性能

事件收集與回調(diào)執(zhí)行:dispatchEventsForPlugins

  • 調(diào)用插件系統(tǒng),從 Fiber 樹提取所有捕獲 / 冒泡事件處理器,存入調(diào)度隊(duì)列
  • 按階段順序執(zhí)行隊(duì)列里的所有事件回調(diào)
function dispatchEventsForPlugins(
  domEventName,
  eventSystemFlags,
  nativeEvent,
  targetInst,
  targetContainer,
) {
  const nativeEventTarget = getEventTarget(nativeEvent);
  // 事件調(diào)度隊(duì)列,數(shù)組結(jié)構(gòu),存儲(chǔ)每條事件處理記錄(包含階段、回調(diào)、Fiber 實(shí)例、合成事件配置等)
  const dispatchQueue = [];
  extractEvents(
    dispatchQueue,
    domEventName,
    targetInst,
    nativeEvent,
    nativeEventTarget,
    eventSystemFlags,
    targetContainer,
  );
  // 按捕獲先、冒泡后的順序遍歷隊(duì)列,創(chuàng)建 SyntheticEvent 合成事件、執(zhí)行用戶回調(diào)、處理事件池、阻止冒泡 / 默認(rèn)行為
  processDispatchQueue(dispatchQueue, eventSystemFlags);
}

作用

  • 調(diào)用 getEventTarget 獲取標(biāo)準(zhǔn)化原生事件目標(biāo) DOM
  • 初始化空事件調(diào)度隊(duì)列 dispatchQueue:存放所有待執(zhí)行的事件處理器。每條隊(duì)列項(xiàng)會(huì)記錄事件階段(捕獲 / 冒泡)、對(duì)應(yīng) Fiber、事件回調(diào)、插件配置等信息
  • 調(diào)用 extractEvents 插件體系提取所有事件處理器,填充隊(duì)列,執(zhí)行完畢后 dispatchQueue 已有序存放全部待執(zhí)行回調(diào)
    • 遍歷 React 內(nèi)置所有事件插件(點(diǎn)擊插件、表單插件、焦點(diǎn)插件、觸摸插件等)
    • 從 targetInst 向上遍歷 Fiber 樹,直到上層限定的邊界 ancestorInst
    • 區(qū)分捕獲階段回調(diào) (onXxxCapture) 、冒泡階段回調(diào) (onXxx) ,分別推入隊(duì)列
    • 插件負(fù)責(zé)映射原生事件與 React 合成事件、處理特殊事件(如 onChange、onSelect)
  • 調(diào)用 processDispatchQueue 按階段順序執(zhí)行隊(duì)列內(nèi)所有事件
    • 區(qū)分捕獲 / 冒泡順序:先執(zhí)行全部捕獲回調(diào),再執(zhí)行全部冒泡回調(diào),模擬 DOM 原生事件傳播模型
    • 復(fù)用事件池,創(chuàng)建 SyntheticEvent 合成事件對(duì)象,掛載原生事件、stopPropagation/preventDefault 封裝方法
    • 依次執(zhí)行用戶寫的事件回調(diào)函數(shù)
    • 監(jiān)聽回調(diào)中調(diào)用 e.stopPropagation(),截?cái)嗪罄m(xù)同階段事件執(zhí)行
    • 回調(diào)執(zhí)行完畢回收合成事件對(duì)象,放回事件池復(fù)用,減少 GC 開銷

設(shè)計(jì)思想

  • 優(yōu)先級(jí)包裝器策略:createEventListenerWrapperWithPriority 根據(jù)事件類型選擇 dispatchDiscreteEvent/dispatchContinuousEvent/dispatchEvent,在原生事件觸發(fā)時(shí)同步設(shè)置 currentUpdatePriority,決定后續(xù) setState 的 lane
  • 被動(dòng)事件仿 document 行為:Chrome 對(duì) document 上的 touchstart/touchmove/wheel 強(qiáng)制 { passive: true },React 19 在 rootContainer 上手動(dòng)模擬(非被動(dòng)時(shí)瀏覽器阻塞滾動(dòng)等待 JS)
  • 偏函數(shù)閉包緩存:bind(null, domEventName, eventSystemFlags, targetContainer) 創(chuàng)建一次性閉包,原生 addEventListener 的 listener 引用在整個(gè) root 生命周期中不變,無需重新綁定

到此這篇關(guān)于React 事件系統(tǒng)addTrappedEventListener的使用的文章就介紹到這了,更多相關(guān)React addTrappedEventListener內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論

西峡县| 龙胜| 玛沁县| 靖州| 平泉县| 彭州市| 阳江市| 泗水县| 唐海县| 常熟市| 正镶白旗| 太湖县| 清苑县| 金门县| 荣昌县| 元氏县| 信丰县| 深水埗区| 临澧县| 武胜县| 剑川县| 隆昌县| 腾冲县| 五指山市| 阜平县| 兰州市| 东平县| 峨山| 丽江市| 黎城县| 台江县| 曲水县| 辽宁省| 胶南市| 涿鹿县| 阿尔山市| 青川县| 宁安市| 蛟河市| 鹿泉市| 宿州市|