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

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

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

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

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

完整調(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)先級包裝器

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

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

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

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

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

// 特性檢測:瀏覽器是否支持 { passive: true } 選項
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;
  }
}

檢測原理

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

設計意義

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

步驟 ③:選擇 addEventListener 重載

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

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

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

優(yōu)先級代表事件包裝器
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)先級)等不匹配上述分類的事件dispatchEvent

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

  • 臨時提升渲染優(yōu)先級,保障交互即時性:針對 click/input/keydown 等離散用戶交互,把事件內(nèi)所有狀態(tài)更新強制設為最高優(yōu)先級,能夠打斷后臺正在執(zhí)行的低優(yōu)先級渲染,解決點擊延遲、輸入卡頓問題,是 React 并發(fā)渲染交互流暢性的核心保障
  • 隔離 startTransition 低優(yōu)上下文:強制清空全局 Transition 標記,確保離散交互不受 startTransition 降級邏輯影響,緊急操作永遠優(yōu)先渲染
  • 上下文壓棧 + 自動恢復,無全局污染:采用先緩存現(xiàn)場 → 修改全局環(huán)境 → 執(zhí)行事件邏輯 → 無論成敗強制還原的范式,不會篡改外部代碼的優(yōu)先級、Transition 環(huán)境,多個事件嵌套、并發(fā)執(zhí)行時上下文互不干擾
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;
  }
}

設計意義

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

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

  • 設置連續(xù)事件專屬調(diào)度優(yōu)先級,平衡流暢性與性能針對滾動、鼠標移動、拖拽、滾輪等每秒多次觸發(fā)的高頻事件,使用中等優(yōu)先級:
    • 不會像離散點擊那樣強行搶占渲染;
    • 多個連續(xù)更新會被調(diào)度器合并,減少重復渲染開銷;
    • 當同時發(fā)生點擊輸入時,離散事件可以打斷滾動渲染,保證用戶主動交互優(yōu)先
  • 剝離 startTransition 低優(yōu)上下文:清空全局 Transition 標記,滾動相關更新不會被當作延遲渲染任務,避免滾動滯后、畫面掉幀
  • 安全隔離全局調(diào)度環(huán)境,無副作用污染:采用緩存現(xiàn)場 → 臨時覆蓋 → 最終強制還原的棧式上下文模式,單次事件執(zhí)行僅局部生效,函數(shù)退出后全局狀態(tài)完全復原,異常場景也不會破壞全局調(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;
  }
}

設計意義

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

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

針對 SSR 水合阻塞場景做分流處理

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

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

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

設計意義

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

查找未水合阻塞 DOM:findInstanceBlockingEvent

  • 專門識別三類會導致事件失效的特殊容器:脫水根、Suspense、Activity
  • 判定是否存在水合阻塞容器,存在則返回阻塞 DOM;
  • 緩存事件目標對應的有效 Fiber 到全局變量,供事件插件系統(tǒng)復用,避免重復查找 DOM-Fiber 映射
  • 當事件觸發(fā)節(jié)點自身未掛載,僅上層祖先掛載時,清空 Fiber 實例,避免事件系統(tǒng)讀取未就緒組件的回調(diào),防止報錯
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 的實例
  let targetInst = getClosestInstanceFromNode(targetNode);

  if (targetInst !== null) {
    // 從當前 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;
      // 應用根 Fiber,對應 #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;
}

作用

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

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

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

  • 本次焦點事件正常分發(fā)執(zhí)行,清空之前緩存的焦點隊列,丟棄未執(zhí)行的舊焦點事件
  • 清空拖拽事件緩存,過期拖拽事件不再補發(fā)
  • 清空鼠標懸浮緩存
  • 只刪除當前觸點對應的緩存事件,不影響其他觸點隊列(指針設備支持多觸點,用 pointerId 區(qū)分不同手指 / 觸控筆)
  • 按觸點 ID 清除對應指針捕獲緩存事件
export function clearIfContinuousEvent(
  domEventName,
  nativeEvent,
) {
  switch (domEventName) {
    case 'focusin':
    case 'focusout':
      // 緩存 focusin /focusout 焦點過渡事件
      queuedFocus = null;
      break;
    case 'dragenter':
    case 'dragleave':
      // 緩存 dragenter /dragleave 拖拽進出事件
      queuedDrag = null;
      break;
    case 'mouseover':
    case 'mouseout':
      // 緩存 mouseover /mouseout 鼠標懸浮進出事件
      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 均為高頻、成對切換、瞬時狀態(tài)過渡事件,短時間會大量連續(xù)觸發(fā);SSR 半水合階段如果立刻執(zhí)行會阻塞主線程,且大量中間事件無業(yè)務價值,只需要保留最新一次回放

  • 匹配到可緩存的連續(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

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

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

作用

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

設計意義

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

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

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

作用

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

設計思想

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

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

相關文章

  • create-react-app修改為多頁面支持的方法

    create-react-app修改為多頁面支持的方法

    本篇文章主要介紹了create-react-app修改為多頁面支持的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-05-05
  • 詳解開發(fā)react應用最好用的腳手架 create-react-app

    詳解開發(fā)react應用最好用的腳手架 create-react-app

    本篇文章主要介紹了詳解開發(fā)react應用最好用的腳手架 create-react-app,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-04-04
  • 在React項目中使用TypeScript詳情

    在React項目中使用TypeScript詳情

    這篇文章主要介紹了在React項目中使用TypeScript詳情,文章通過圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下
    2022-09-09
  • React使用xlsx和js-export-excel實現(xiàn)前端導出

    React使用xlsx和js-export-excel實現(xiàn)前端導出

    這篇文章主要為大家詳細介紹了React如何分別使用xlsx和js-export-excel實現(xiàn)前端導出功能,文中的示例代碼講解詳細,感興趣的小伙伴可以了解下
    2024-02-02
  • react antd-mobile ActionSheet+tag實現(xiàn)多選方式

    react antd-mobile ActionSheet+tag實現(xiàn)多選方式

    這篇文章主要介紹了react antd-mobile ActionSheet+tag實現(xiàn)多選方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-10-10
  • React Native自定義Android的SSL證書鏈校驗

    React Native自定義Android的SSL證書鏈校驗

    這篇文章主要為大家介紹了React Native自定義Android的SSL證書鏈校驗示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-10-10
  • React Native自定義控件底部抽屜菜單的示例

    React Native自定義控件底部抽屜菜單的示例

    本篇文章主要介紹了React Native自定義控件底部抽屜菜單的示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-02-02
  • React Form組件的實現(xiàn)封裝雜談

    React Form組件的實現(xiàn)封裝雜談

    這篇文章主要介紹了React Form組件的實現(xiàn)封裝雜談,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-05-05
  • 關于react ant 組件 Select下拉框 值回顯的問題

    關于react ant 組件 Select下拉框 值回顯的問題

    這篇文章主要介紹了關于react ant 組件 Select下拉框 值回顯的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • 關于react 父子組件的執(zhí)行順序

    關于react 父子組件的執(zhí)行順序

    這篇文章主要介紹了關于react 父子組件的執(zhí)行順序,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-09-09

最新評論

通化市| 仁怀市| 彰武县| 青河县| 漠河县| 通城县| 禹州市| 开原市| 南和县| 喀喇| 泰安市| 昂仁县| 西平县| 永靖县| 山东| 囊谦县| 庄河市| 绥德县| 手机| 麻栗坡县| 金堂县| 丹寨县| 青铜峡市| 饶河县| 台东县| 吴旗县| 手游| 密云县| 翼城县| 通州区| 武冈市| 呼和浩特市| 平原县| 黔西| 青冈县| 庆城县| 兴安县| 石林| 图木舒克市| 托克逊县| 孙吴县|