React

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > React > React  addTrappedEventListener

React 事件系统addTrappedEventListener的使用

作者:YFF菲菲兔

本文主要介绍了React 事件系统addTrappedEventListener的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

React 合成事件系统最底层 DOM 监听器创建与绑定核心函数

完整调用链路全览

──────────────────────────────────────────────────────────────────────
 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")

源码解析

步骤 ①:创建优先级包装器

生成非原生、带 React 调度能力的拦截回调:原生 DOM 触发事件时,不会直接执行组件 onClick,而是先走这个 wrapper。把原生 DOM 事件与 ``React 内部渲染调度、合成事件机制解耦,所有底层监听统一使用标准化包装函数,上层不用关心调度细节

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

步骤 ②:被动事件优化

浏览器支持 passive 配置时,滚动 / 触摸类事件强制设为 passive: true。自动做滚动性能优化,开发者无需手动配置,抹平浏览器滚动卡顿缺陷

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 触发 → 浏览器支持
      },
    });
    window.addEventListener('test', options, options);
    window.removeEventListener('test', options, options);
  } catch (e) {
    passiveBrowserEventsSupported = false;
  }
}

检测原理

addEventListener 第三个参数可以传入 { passive: true }。如果浏览器支持,options.passive 的 getter 会被触发,passiveBrowserEventsSupported 设为 true。如果浏览器不支持此选项(如 IE ≤ 11),catch 块设为 false

设计意义

Chrome 56+ 将 touchstart/touchmove/wheel 的 document 级监听器默认设为 { passive: true }(因为开发者几乎不会在 touch 事件上调用 preventDefault 但必须等待 JS 执行判断是否调用)。React 19 不把事件绑到 document 而绑到 rootContainer,手动模拟这个 passive 行为以获得相同性能收益

步骤 ③:选择 addEventListener 重载

根据捕获 / 冒泡 + 是否 passive 分流到 4 个底层绑定工具,统一管理 DOM addEventListener 参数差异,保存解绑函数,支撑一次性监听自动移除,为后续全局卸载根容器事件提供基础能力

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;
}

四种组合

isCapturePhaseListenerisPassiveListener调用的函数DOM 参数
truetrueaddEventCaptureListenerWithPassiveFlag{ capture: true, passive: true }
trueundefinedaddEventCaptureListenertrue
falsetrueaddEventBubbleListenerWithPassiveFlag{ passive: true }
falseundefinedaddEventBubbleListenerfalse

辅助函数

创建优先级事件监听器: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,
  );
}

作用

事件优先级映射(getEventPriority)

优先级代表事件包装器
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(特定调度优先级)等不匹配上述分类的事件dispatchEvent

离散事件的调度包装器:dispatchDiscreteEvent

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 内部,点击 / 输入这类紧急交互也要走高优渲染,不能被降级延迟,事件执行过程中产生的所有 setState 更新,都会标记为高优,可以中断正在进行的后台渲染,保证交互即时响应

连续事件的调度包装器:dispatchContinuousEvent

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;
  }
}

设计意义

滚动、鼠标移动这类持续交互,不降级为 transition 更新,使用专属连续优先级(优先级低于点击、输入等离散事件),后台渲染任务遇到离散交互会被直接抢占中断,连续事件之间会合并调度,减少重复渲染,防止高频事件卡死页面。高频滚动事件不抢占主线程,避免短时间大量渲染阻塞页面

事件分发中枢控制器:dispatchEvent

针对 SSR 水合阻塞场景做分流处理

export function dispatchEvent(
  domEventName,
  eventSystemFlags,
  targetContainer,
  nativeEvent,
) {
  // 标记 React 合成事件系统是否启用;关闭则直接跳过所有事件处理
  if (!_enabled) {
    return;
  }
  // 查找当前触发事件的 DOM 节点链上,还未完成水合(hydrate)的 DOM 元素;返回对应 DOM,无阻塞节点返回 null
  let blockedOn = findInstanceBlockingEvent(nativeEvent);
  if (blockedOn === null) {
    dispatchEventForPluginEventSystem(
      domEventName,
      eventSystemFlags,
      nativeEvent,
      return_targetInst,  // 事件目标 DOM 对应的 Fiber 实例
      targetContainer,
    );
    // 清空当前连续事件缓存队列,丢弃过期事件
    clearIfContinuousEvent(domEventName, nativeEvent);
    return;
  }

  if (
    // 连续事件(scroll/mousemove/touchmove)入队
    queueIfContinuousEvent(
      blockedOn,
      domEventName,
      eventSystemFlags,
      targetContainer,
      nativeEvent,
    )
  ) {
    nativeEvent.stopPropagation();
    return;
  }
  clearIfContinuousEvent(domEventName, nativeEvent);

  if (
    eventSystemFlags & IS_CAPTURE_PHASE &&
    // 判断该离散事件是否必须等待水合完成才能执行(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,
  );
}

作用

设计意义

查找未水合阻塞 DOM:findInstanceBlockingEvent

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 组件,子树延迟加载,内部 DOM 已渲染但未激活
      if (tag === SuspenseComponent) {
        const instance = getSuspenseInstanceFromFiber(nearestMounted);
        if (instance !== null) {
          return instance;
        }
        targetInst = null;
      // 离屏缓存组件(React 内部),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;
}

作用

清理缓存的过渡类事件:clearIfContinuousEvent

清理 SSR 半水合阶段积压的过渡类事件缓存,最终只执行最新一次交互事件,中间滑动产生的过渡事件全部作废。当页面处于半水合状态时,queueIfContinuousEvent 会把频繁触发的悬浮、焦点、拖拽、指针事件存入全局队列延迟执行;一旦当前事件可以正常分发(组件已水合、无阻塞),调用本函数清空对应缓存,避免过期事件后续补发造成状态错乱

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 均为高频、成对切换、瞬时状态过渡事件,短时间会大量连续触发;SSR 半水合阶段如果立刻执行会阻塞主线程,且大量中间事件无业务价值,只需要保留最新一次回放

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;
}

连续过渡事件缓存工具:accumulateOrCreateContinuousQueuedReplayableEvent

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 打上连续事件水合优先级,调度器优先处理该组件水合,尽快解除阻塞
        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

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 等常规事件,不是 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;
      }
    }
  }
  // 批量更新,内部回调所有 setState 会合并批量渲染,减少重渲染
  batchedUpdates(() =>
    // 事件插件体系真正执行逻辑,生成合成事件、遍历 Fiber 执行 onXxx 回调
    dispatchEventsForPlugins(
      domEventName,
      eventSystemFlags,
      nativeEvent,
      ancestorInst,
      targetContainer,
    ),
  );
}

作用

设计意义

事件收集与回调执行:dispatchEventsForPlugins

function dispatchEventsForPlugins(
  domEventName,
  eventSystemFlags,
  nativeEvent,
  targetInst,
  targetContainer,
) {
  const nativeEventTarget = getEventTarget(nativeEvent);
  // 事件调度队列,数组结构,存储每条事件处理记录(包含阶段、回调、Fiber 实例、合成事件配置等)
  const dispatchQueue = [];
  extractEvents(
    dispatchQueue,
    domEventName,
    targetInst,
    nativeEvent,
    nativeEventTarget,
    eventSystemFlags,
    targetContainer,
  );
  // 按捕获先、冒泡后的顺序遍历队列,创建 SyntheticEvent 合成事件、执行用户回调、处理事件池、阻止冒泡 / 默认行为
  processDispatchQueue(dispatchQueue, eventSystemFlags);
}

作用

设计思想

到此这篇关于React 事件系统addTrappedEventListener的使用的文章就介绍到这了,更多相关React addTrappedEventListener内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文