text: /**
   * Copyright (c) Facebook, Inc. and its affiliates.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   *
   * @flow
   */
  
  import type {DOMEventName} from './DOMEventNames';
  import {
    type EventSystemFlags,
    SHOULD_NOT_DEFER_CLICK_FOR_FB_SUPPORT_MODE,
    IS_LEGACY_FB_SUPPORT_MODE,
    SHOULD_NOT_PROCESS_POLYFILL_EVENT_PLUGINS,
  } from './EventSystemFlags';
  import type {AnyNativeEvent} from './PluginModuleType';
  import type {
    KnownReactSyntheticEvent,
    ReactSyntheticEvent,
  } from './ReactSyntheticEventType';
  import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
  //事件注册
  import {allNativeEvents} from './EventRegistry';
  import {
    IS_CAPTURE_PHASE,
    IS_EVENT_HANDLE_NON_MANAGED_NODE,
    IS_NON_DELEGATED,
  } from './EventSystemFlags';
  import {isReplayingEvent} from './CurrentReplayingEvent';
  
  import {
    HostRoot,
    HostPortal,
    HostComponent,
    HostText,
    ScopeComponent,
  } from 'react-reconciler/src/ReactWorkTags';
  
  import getEventTarget from './getEventTarget';
  import {
    getClosestInstanceFromNode,
    getEventListenerSet,
    getEventHandlerListeners,
  } from '../client/ReactDOMComponentTree';
  import {COMMENT_NODE} from '../shared/HTMLNodeType';
  import {batchedUpdates} from './ReactDOMUpdateBatching';
  import getListener from './getListener';
  import {passiveBrowserEventsSupported} from './checkPassiveEvents';
  
  import {
    enableLegacyFBSupport,
    enableCreateEventHandleAPI,
    enableScopeAPI,
  } from 'shared/ReactFeatureFlags';
  import {
    invokeGuardedCallbackAndCatchFirstError,
    rethrowCaughtError,
  } from 'shared/ReactErrorUtils';
  import {DOCUMENT_NODE} from '../shared/HTMLNodeType';
  import {createEventListenerWrapperWithPriority} from './ReactDOMEventListener';
  import {
    removeEventListener,
    addEventCaptureListener,
    addEventBubbleListener,
    addEventBubbleListenerWithPassiveFlag,
    addEventCaptureListenerWithPassiveFlag,
  } from './EventListener';
  import * as BeforeInputEventPlugin from './plugins/BeforeInputEventPlugin';
  import * as ChangeEventPlugin from './plugins/ChangeEventPlugin';
  import * as EnterLeaveEventPlugin from './plugins/EnterLeaveEventPlugin';
  import * as SelectEventPlugin from './plugins/SelectEventPlugin';
  import * as SimpleEventPlugin from './plugins/SimpleEventPlugin';
  
  type DispatchListener = {|
    instance: null | Fiber,
    listener: Function,
    currentTarget: EventTarget,
  |};
  
  type DispatchEntry = {|
    event: ReactSyntheticEvent,
    listeners: Array<DispatchListener>,
  |};
  
  export type DispatchQueue = Array<DispatchEntry>;
  //各种事件的注册
  // 通过插件注册事件
  // 原生DOM事件名称与React事件名称映射关系
  // SimpleEventPlugin 是事件系统的基本功能，是事件系统的核心
  // 其它插件本质上是 polyfills，在没有这些 polyfill 插件的情况下可允许发布 React 的构建
  // TODO: remove top-level side effect.
  SimpleEventPlugin.registerEvents();
  EnterLeaveEventPlugin.registerEvents();
  ChangeEventPlugin.registerEvents();
  SelectEventPlugin.registerEvents();
  BeforeInputEventPlugin.registerEvents();
  /*
  * 在 DOMPluginEventSystem.js 文件的 extractEvents 函数中，
  * 主要调用了React的 5 种事件插件来收集listener。
  * 其中 SimpleEventPlugin 插件是React事件系统的核心，
  * 提供了React事件系统的基本功能，其它四个事件插件本质上是 polyfills，
  * 在没有这些 polyfill 插件的情况下可允许发布 React 的构建。
  
  * */
  function extractEvents(
    dispatchQueue: DispatchQueue,
    domEventName: DOMEventName,
    targetInst: null | Fiber,
    nativeEvent: AnyNativeEvent,
    nativeEventTarget: null | EventTarget,
    eventSystemFlags: EventSystemFlags,
    targetContainer: EventTarget,
  ) {
    // TODO: we should remove the concept of a "SimpleEventPlugin".
    // This is the basic functionality of the event system. All
    // the other plugins are essentially polyfills. So the plugin
    // should probably be inlined somewhere and have its logic
    // be core the to event system. This would potentially allow
    // us to ship builds of React without the polyfilled plugins below.
    // SimpleEventPlugin 是事件系统的基本功能，是事件系统的核心
    SimpleEventPlugin.extractEvents(
      dispatchQueue,
      domEventName,
      targetInst,
      nativeEvent,
      nativeEventTarget,
      eventSystemFlags,
      targetContainer,
    );
    const shouldProcessPolyfillPlugins =
      (eventSystemFlags & SHOULD_NOT_PROCESS_POLYFILL_EVENT_PLUGINS) === 0;
    // We don't process these events unless we are in the
    // event's native "bubble" phase, which means that we're
    // not in the capture phase. That's because we emulate
    // the capture phase here still. This is a trade-off,
    // because in an ideal world we would not emulate and use
    // the phases properly, like we do with the SimpleEvent
    // plugin. However, the plugins below either expect
    // emulation (EnterLeave) or use state localized to that
    // plugin (BeforeInput, Change, Select). The state in
    // these modules complicates things, as you'll essentially
    // get the case where the capture phase event might change
    // state, only for the following bubble event to come in
    // later and not trigger anything as the state now
    // invalidates the heuristics of the event plugin. We
    // could alter all these plugins to work in such ways, but
    // that might cause other unknown side-effects that we
    // can't foresee right now.
    if (shouldProcessPolyfillPlugins) {
      // 下面的几类插件本质上是 polyfills，在没有这些 polyfill 插件的情况下可允许发布 React 的构建
      EnterLeaveEventPlugin.extractEvents(
        dispatchQueue,
        domEventName,
        targetInst,
        nativeEvent,
        nativeEventTarget,
        eventSystemFlags,
        targetContainer,
      );
      ChangeEventPlugin.extractEvents(
        dispatchQueue,
        domEventName,
        targetInst,
        nativeEvent,
        nativeEventTarget,
        eventSystemFlags,
        targetContainer,
      );
      SelectEventPlugin.extractEvents(
        dispatchQueue,
        domEventName,
        targetInst,
        nativeEvent,
        nativeEventTarget,
        eventSystemFlags,
        targetContainer,
      );
      BeforeInputEventPlugin.extractEvents(
        dispatchQueue,
        domEventName,
        targetInst,
        nativeEvent,
        nativeEventTarget,
        eventSystemFlags,
        targetContainer,
      );
    }
  }
  
  // List of events that need to be individually attached to media elements.
  export const mediaEventTypes: Array<DOMEventName> = [
    'abort',
    'canplay',
    'canplaythrough',
    'durationchange',
    'emptied',
    'encrypted',
    'ended',
    'error',
    'loadeddata',
    'loadedmetadata',
    'loadstart',
    'pause',
    'play',
    'playing',
    'progress',
    'ratechange',
    'resize',
    'seeked',
    'seeking',
    'stalled',
    'suspend',
    'timeupdate',
    'volumechange',
    'waiting',
  ];
  
  // We should not delegate these events to the container, but rather
  // set them on the actual target element itself. This is primarily
  // because these events do not consistently bubble in the DOM.
  export const nonDelegatedEvents: Set<DOMEventName> = new Set([
    'cancel',
    'close',
    'invalid',
    'load',
    'scroll',
    'toggle',
    // In order to reduce bytes, we insert the above array of media events
    // into this Set. Note: the "error" event isn't an exclusive media event,
    // and can occur on other elements too. Rather than duplicate that event,
    // we just take it from the media events array.
    ...mediaEventTypes,
  ]);
  
  function executeDispatch(
    event: ReactSyntheticEvent,
    listener: Function,
    currentTarget: EventTarget,
  ): void {
    const type = event.type || 'unknown-event';
    event.currentTarget = currentTarget;
    invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event);
    event.currentTarget = null;
  }
  
  function processDispatchQueueItemsInOrder(
    event: ReactSyntheticEvent,
    dispatchListeners: Array<DispatchListener>,
    inCapturePhase: boolean,
  ): void {
    let previousInstance;
    if (inCapturePhase) {
       // 1、捕获阶段 事件处理
      // 注意这里是倒叙遍历 listener
      for (let i = dispatchListeners.length - 1; i >= 0; i--) {
        const {instance, currentTarget, listener} = dispatchListeners[i];
        if (instance !== previousInstance && event.isPropagationStopped()) {
          return;
        }
        executeDispatch(event, listener, currentTarget);
        previousInstance = instance;
      }
    } else {
      // 2、冒泡阶段 事件处理
      // 注意这里是正序遍历 listener
      for (let i = 0; i < dispatchListeners.length; i++) {
        const {instance, currentTarget, listener} = dispatchListeners[i];
        if (instance !== previousInstance && event.isPropagationStopped()) {
          return;
        }
        executeDispatch(event, listener, currentTarget);
        previousInstance = instance;
      }
    }
  }
  
  export function processDispatchQueue(
    dispatchQueue: DispatchQueue,
    eventSystemFlags: EventSystemFlags,
  ): void {
    const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
    for (let i = 0; i < dispatchQueue.length; i++) {
      const {event, listeners} = dispatchQueue[i];
      // 对合成事件进行派发
      processDispatchQueueItemsInOrder(event, listeners, inCapturePhase);
      //  event system doesn't use pooling.
    }
    // This would be a good time to rethrow if any of the event handlers threw.
    rethrowCaughtError();
  }
  /*
  * 在 dispatchEventsForPlugins 函数中，做了4件事情：
   首先调用getEventTarget函数获取触发事件的原生DOM节点。
   然后定义dispatchQueue队列，用于存储所有的listener。
   接着调用extractEvents方法收集所有的listener，存入dispatchQueue队列中。
   最后调用processDispatchQueue函数，执行事件派发。
  
   作者：紫圣
   链接：https://juejin.cn/post/7031538616346083359
   来源：稀土掘金
   著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
  * */
  function dispatchEventsForPlugins(
    domEventName: DOMEventName,
    eventSystemFlags: EventSystemFlags,
    nativeEvent: AnyNativeEvent,
    targetInst: null | Fiber,
    targetContainer: EventTarget,
  ): void {
    // 1、定位触发事件的原生DOM节点
    const nativeEventTarget = getEventTarget(nativeEvent);
    // 2、批量处理队列
    const dispatchQueue: DispatchQueue = [];
    // 3、收集 所有的listener
    /* 
       dispatchQueue.push({event, listeners});
    */
    extractEvents(
      dispatchQueue,
      domEventName,
      targetInst,
      nativeEvent,
      nativeEventTarget,
      eventSystemFlags,
      targetContainer,
    );
    // 4、执行派发
    processDispatchQueue(dispatchQueue, eventSystemFlags);
  }
  
  export function listenToNonDelegatedEvent(
    domEventName: DOMEventName,
    targetElement: Element,
  ): void {
    if (__DEV__) {
      if (!nonDelegatedEvents.has(domEventName)) {
        console.error(
          'Did not expect a listenToNonDelegatedEvent() call for "%s". ' +
          'This is a bug in React. Please file an issue.',
          domEventName,
        );
      }
    }
    const isCapturePhaseListener = false;
    const listenerSet = getEventListenerSet(targetElement);
    const listenerSetKey = getListenerSetKey(
      domEventName,
      isCapturePhaseListener,
    );
    if (!listenerSet.has(listenerSetKey)) {
      addTrappedEventListener(
        targetElement,
        domEventName,
        IS_NON_DELEGATED,
        isCapturePhaseListener,
      );
      listenerSet.add(listenerSetKey);
    }
  }
  
  /*
  * domEventName           事件名称
  * isCapturePhaseListener 是否冒泡
  * target                 绑定的对象
  * */
  export function listenToNativeEvent(
    domEventName: DOMEventName,
    isCapturePhaseListener: boolean,
    target: EventTarget,
  ): void {
    if (__DEV__) {
      if (nonDelegatedEvents.has(domEventName) && !isCapturePhaseListener) {
        console.error(
          'Did not expect a listenToNativeEvent() call for "%s" in the bubble phase. ' +
          'This is a bug in React. Please file an issue.',
          domEventName,
        );
      }
    }
  
    let eventSystemFlags = 0;
    if (isCapturePhaseListener) {
      eventSystemFlags |= IS_CAPTURE_PHASE; //000000|000100 => 000100
    }
    addTrappedEventListener(
      target,
      domEventName,
      eventSystemFlags,
      isCapturePhaseListener,
    );
  }
  
  // This is only used by createEventHandle when the
  // target is not a DOM element. E.g. window.
  export function listenToNativeEventForNonManagedEventTarget(
    domEventName: DOMEventName,
    isCapturePhaseListener: boolean,
    target: EventTarget,
  ): void {
    let eventSystemFlags = IS_EVENT_HANDLE_NON_MANAGED_NODE;
    const listenerSet = getEventListenerSet(target);
    const listenerSetKey = getListenerSetKey(
      domEventName,
      isCapturePhaseListener,
    );
    if (!listenerSet.has(listenerSetKey)) {
      if (isCapturePhaseListener) {
        eventSystemFlags |= IS_CAPTURE_PHASE;
      }
      addTrappedEventListener(
        target,
        domEventName,
        eventSystemFlags,
        isCapturePhaseListener,
      );
      listenerSet.add(listenerSetKey);
    }
  }
  
  //_reactListening+34位随机数
  const listeningMarker =
    '_reactListening' +
    Math.random()
      .toString(36)
      .slice(2);
  //const rootContainerElement =
  //     container.nodeType === COMMENT_NODE ? container.parentNode : container;
  export function listenToAllSupportedEvents(rootContainerElement: EventTarget) {
    if (!(rootContainerElement: any)[listeningMarker]) {
      (rootContainerElement: any)[listeningMarker] = true;
      allNativeEvents.forEach(domEventName => {
        // We handle selectionchange separately because it
        // doesn't bubble and needs to be on the document.
        //我们单独处理selectionchange，因为它不冒泡，需要放在document上。
        if (domEventName !== 'selectionchange') {
          if (!nonDelegatedEvents.has(domEventName)) {
            listenToNativeEvent(domEventName, false, rootContainerElement);
          }
          listenToNativeEvent(domEventName, true, rootContainerElement);
        }
      });
      const ownerDocument =
        (rootContainerElement: any).nodeType === DOCUMENT_NODE
          ? rootContainerElement
          : (rootContainerElement: any).ownerDocument;
      if (ownerDocument !== null) {
        // The selectionchange event also needs deduplication
        // but it is attached to the document.
        // selectionchange事件只能绑定在document上
        if (!(ownerDocument: any)[listeningMarker]) {
          (ownerDocument: any)[listeningMarker] = true;
          listenToNativeEvent('selectionchange', false, ownerDocument);
        }
      }
    }
  }
  
  function addTrappedEventListener(
    targetContainer: EventTarget,
    domEventName: DOMEventName,
    eventSystemFlags: EventSystemFlags,
    isCapturePhaseListener: boolean,
    isDeferredListenerForLegacyFBSupport?: boolean,
  ) {
    //根据优先级创建事件监听的容器
    //给不同的事件放置不同的优先级
    //createEventListenerWrapperWithPriority返回的是一个函数 参数是nativeEvent(原生事件)
    //
    let listener = createEventListenerWrapperWithPriority(
      targetContainer,
      domEventName,
      eventSystemFlags,
    );
    // If passive option is not supported, then the event will be
    // active and not passive.
    let isPassiveListener = undefined;
    if (passiveBrowserEventsSupported) {
      // Browsers introduced an intervention, making these events
      // passive by default on document. React doesn't bind them
      // to document anymore, but changing this now would undo
      // the performance wins from the change. So we emulate
      // the existing behavior manually on the roots now.
      // https://github.com/facebook/react/issues/19651
      // 浏览器引入了干预措施，使这些事件绑定在document上的时候，默认不会执行preventDefault方法,
      // React没有将它们绑定到document上，
      // 但如果不默认不执行preventDefault，将取消更改带来的性能优势。
      // 所以我们现在在根上手动模拟现有这个的行为。
      if (
        domEventName === 'touchstart' ||
        domEventName === 'touchmove' ||
        domEventName === 'wheel'
      ) {
        isPassiveListener = true;
      }
    }
  
    targetContainer =
      enableLegacyFBSupport && isDeferredListenerForLegacyFBSupport
        ? (targetContainer: any).ownerDocument
        : targetContainer;
  
    let unsubscribeListener;
    // When legacyFBSupport is enabled, it's for when we
    // want to add a one time event listener to a container.
    // This should only be used with enableLegacyFBSupport
    // due to requirement to provide compatibility with
    // internal FB www event tooling. This works by removing
    // the event listener as soon as it is invoked. We could
    // also attempt to use the {once: true} param on
    // addEventListener, but that requires support and some
    // browsers do not support this today, and given this is
    // to support legacy code patterns, it's likely they'll
    // need support for such browsers.
    //添加一次性的事件，触发之后就移除。
    //当legacyFBSupport启用时，它适用于我们希望向容器添加一次性事件侦听器的情况。
    //由于需要提供与内部FB www事件工具的兼容性，因此只能与enableLegacyFBSupport一起使用。
    //这是通过在事件侦听器被调用后立即删除它来实现的。
    //我们也可以尝试在addEventListener上使用{once:true}参数，但这需要支持，
    //而且一些浏览器现在不支持这个，考虑到这是为了支持遗留代码模式，他们可能需要对此类浏览器的支持。
  
    //简单来说 就是为了做兼容 一次性事件侦听器的情况
    if (enableLegacyFBSupport && isDeferredListenerForLegacyFBSupport) {
      const originalListener = listener;
      listener = function (...p) {
        removeEventListener(
          targetContainer,
          domEventName,
          unsubscribeListener,
          isCapturePhaseListener,
        );
        return originalListener.apply(this, p);
      };
    }
    // TODO: There are too many combinations here. Consolidate them.
    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,
        );
      }
    }
  }
  
  function deferClickToDocumentForLegacyFBSupport(
    domEventName: DOMEventName,
    targetContainer: EventTarget,
  ): void {
    // We defer all click events with legacy FB support mode on.
    // This means we add a one time event listener to trigger
    // after the FB delegated listeners fire.
    const isDeferredListenerForLegacyFBSupport = true;
    addTrappedEventListener(
      targetContainer,
      domEventName,
      IS_LEGACY_FB_SUPPORT_MODE,
      false,
      isDeferredListenerForLegacyFBSupport,
    );
  }
  
  function isMatchingRootContainer(
    grandContainer: Element,
    targetContainer: EventTarget,
  ): boolean {
    return (
      grandContainer === targetContainer ||
      (grandContainer.nodeType === COMMENT_NODE &&
        grandContainer.parentNode === targetContainer)
    );
  }
  /*
  * dispatchEventForPluginEventSystem函数的作用是通过事件插件系统派发事件。
  * 在该函数中，为了兼容16以下的版本，会把click事件监听器注册到 document 上。
  * 然后从当前触发事件的DOM节点开始，向上遍历fiber树，找到根DOM容器，
  * 最后调用dispatchEventsForPlugins函数来派发事件。
  
   作者：紫圣
   链接：https://juejin.cn/post/7031538616346083359
   来源：稀土掘金
   著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。*/
  // 通过插件系统，派发事件
  export function dispatchEventForPluginEventSystem(
    domEventName: DOMEventName,
    eventSystemFlags: EventSystemFlags,
    nativeEvent: AnyNativeEvent,
    targetInst: null | Fiber,
    targetContainer: EventTarget,
  ): void {
    let ancestorInst = targetInst;
    if (
      (eventSystemFlags & IS_EVENT_HANDLE_NON_MANAGED_NODE) === 0 &&
      (eventSystemFlags & IS_NON_DELEGATED) === 0
    ) {
      const targetContainerNode = ((targetContainer: any): Node);
  
      // If we are using the legacy FB support flag, we
      // defer the event to the null with a one
      // time event listener so we can defer the event.
      // 兼容旧版 FB support flag，使用一个事件监听器
      if (
        enableLegacyFBSupport &&
        // If our event flags match the required flags for entering
        // FB legacy mode and we are processing the "click" event,
        // then we can defer the event to the "document", to allow
        // for legacy FB support, where the expected behavior was to
        // match React < 16 behavior of delegated clicks to the doc.
          // 兼容  React < 16 的版本
          // click 事件注册到 document 上
        domEventName === 'click' &&
        (eventSystemFlags & SHOULD_NOT_DEFER_CLICK_FOR_FB_SUPPORT_MODE) === 0 &&
        !isReplayingEvent(nativeEvent)
      ) {
        // 在 document 上注册事件监听器
        deferClickToDocumentForLegacyFBSupport(domEventName, targetContainer);
        return;
      }
      if (targetInst !== null) {
        // The below logic attempts to work out if we need to change
        // the target fiber to a different ancestor. We had similar logic
        // in the legacy event system, except the big difference between
        // systems is that the modern event system now has an event listener
        // attached to each React Root and React Portal Root. Together,
        // the DOM nodes representing these roots are the "rootContainer".
        // To figure out which ancestor instance we should use, we traverse
        // up the fiber tree from the target instance and attempt to find
        // root boundaries that match that of our current "rootContainer".
        // If we find that "rootContainer", we find the parent fiber
        // sub-tree for that root and make that our ancestor instance.
        let node = targetInst;
        // 从当前触发事件的DOM节点开始，遍历fiber树，找到根DOM容器
        mainLoop: while (true) {
          if (node === null) {
            return;
          }
          const nodeTag = node.tag;
          if (nodeTag === HostRoot || nodeTag === HostPortal) {
            let container = node.stateNode.containerInfo;
            if (isMatchingRootContainer(container, targetContainerNode)) {
              break;
            }
            if (nodeTag === HostPortal) {
              // The target is a portal, but it's not the rootContainer we're looking for.
              // Normally portals handle their own events all the way down to the root.
              // So we should be able to stop now. However, we don't know if this portal
              // was part of *our* root.
              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)
                  ) {
                    // This is the rootContainer we're looking for and we found it as
                    // a parent of the Portal. That means we can ignore it because the
                    // Portal will bubble through to us.
                    return;
                  }
                }
                grandNode = grandNode.return;
              }
            }
            // Now we need to find it's corresponding host fiber in the other
            // tree. To do this we can use getClosestInstanceFromNode, but we
            // need to validate that the fiber is a host instance, otherwise
            // we need to traverse up through the DOM till we find the correct
            // node that is from the other tree.
            while (container !== null) {
              const parentNode = getClosestInstanceFromNode(container);
              if (parentNode === null) {
                return;
              }
              const parentTag = parentNode.tag;
              if (parentTag === HostComponent || parentTag === HostText) {
                node = ancestorInst = parentNode;
                continue mainLoop;
              }
              container = container.parentNode;
            }
          }
          node = node.return;
        }
      }
    }
    // 批量处理
    batchedUpdates(() =>
      dispatchEventsForPlugins(
        domEventName,
        eventSystemFlags,
        nativeEvent,
        ancestorInst,
        targetContainer,
      ),
    );
  }
  
  function createDispatchListener(
    instance: null | Fiber,
    listener: Function,
    currentTarget: EventTarget,
  ): DispatchListener {
    return {
      instance,
      listener,
      currentTarget,
    };
  }
  
  export function accumulateSinglePhaseListeners(
    targetFiber: Fiber | null,
    reactName: string | null,
    nativeEventType: string,
    inCapturePhase: boolean,
    accumulateTargetOnly: boolean,
    nativeEvent: AnyNativeEvent,
  ): Array<DispatchListener> {
    const captureName = reactName !== null ? reactName + 'Capture' : null;
    const reactEventName = inCapturePhase ? captureName : reactName;
    let listeners: Array<DispatchListener> = [];
  
    let instance = targetFiber;
    let lastHostComponent = null;
  
    // Accumulate all instances and listeners via the target -> root path.
    while (instance !== null) {
      const {stateNode, tag} = instance;
      // Handle listeners that are on HostComponents (i.e. <div>)
      if (tag === HostComponent && stateNode !== null) {
        lastHostComponent = stateNode;
  
        // createEventHandle listeners
        if (enableCreateEventHandleAPI) {
          const eventHandlerListeners = getEventHandlerListeners(
            lastHostComponent,
          );
          if (eventHandlerListeners !== null) {
            eventHandlerListeners.forEach(entry => {
              if (
                entry.type === nativeEventType &&
                entry.capture === inCapturePhase
              ) {
                listeners.push(
                  createDispatchListener(
                    instance,
                    entry.callback,
                    (lastHostComponent: any),
                  ),
                );
              }
            });
          }
        }
  
        // Standard React on* listeners, i.e. onClick or onClickCapture
        if (reactEventName !== null) {
          const listener = getListener(instance, reactEventName);
          if (listener != null) {
            listeners.push(
              createDispatchListener(instance, listener, lastHostComponent),
            );
          }
        }
      } else if (
        enableCreateEventHandleAPI &&
        enableScopeAPI &&
        tag === ScopeComponent &&
        lastHostComponent !== null &&
        stateNode !== null
      ) {
        // Scopes
        const reactScopeInstance = stateNode;
        const eventHandlerListeners = getEventHandlerListeners(
          reactScopeInstance,
        );
        if (eventHandlerListeners !== null) {
          eventHandlerListeners.forEach(entry => {
            if (
              entry.type === nativeEventType &&
              entry.capture === inCapturePhase
            ) {
              listeners.push(
                createDispatchListener(
                  instance,
                  entry.callback,
                  (lastHostComponent: any),
                ),
              );
            }
          });
        }
      }
      // If we are only accumulating events for the target, then we don't
      // continue to propagate through the React fiber tree to find other
      // listeners.
      if (accumulateTargetOnly) {
        break;
      }
      // If we are processing the onBeforeBlur event, then we need to take
      // into consideration that part of the React tree might have been hidden
      // or deleted (as we're invoking this event during commit). We can find
      // this out by checking if intercept fiber set on the event matches the
      // current instance fiber. In which case, we should clear all existing
      // listeners.
      if (enableCreateEventHandleAPI && nativeEvent.type === 'beforeblur') {
        // $FlowFixMe: internal field
        const detachedInterceptFiber = nativeEvent._detachedInterceptFiber;
        if (
          detachedInterceptFiber !== null &&
          (detachedInterceptFiber === instance ||
            detachedInterceptFiber === instance.alternate)
        ) {
          listeners = [];
        }
      }
      instance = instance.return;
    }
    return listeners;
  }
  
  // We should only use this function for:
  // - BeforeInputEventPlugin
  // - ChangeEventPlugin
  // - SelectEventPlugin
  // This is because we only process these plugins
  // in the bubble phase, so we need to accumulate two
  // phase event listeners (via emulation).
  export function accumulateTwoPhaseListeners(
    targetFiber: Fiber | null,
    reactName: string,
  ): Array<DispatchListener> {
    const captureName = reactName + 'Capture';
    const listeners: Array<DispatchListener> = [];
    let instance = targetFiber;
  
    // Accumulate all instances and listeners via the target -> root path.
    while (instance !== null) {
      const {stateNode, tag} = instance;
      // Handle listeners that are on HostComponents (i.e. <div>)
      if (tag === HostComponent && stateNode !== null) {
        const currentTarget = stateNode;
        const captureListener = getListener(instance, captureName);
        if (captureListener != null) {
          listeners.unshift(
            createDispatchListener(instance, captureListener, currentTarget),
          );
        }
        const bubbleListener = getListener(instance, reactName);
        if (bubbleListener != null) {
          listeners.push(
            createDispatchListener(instance, bubbleListener, currentTarget),
          );
        }
      }
      instance = instance.return;
    }
    return listeners;
  }
  
  function getParent(inst: Fiber | null): Fiber | null {
    if (inst === null) {
      return null;
    }
    do {
      inst = inst.return;
      // TODO: If this is a HostRoot we might want to bail out.
      // That is depending on if we want nested subtrees (layers) to bubble
      // events to their parent. We could also go through parentNode on the
      // host node but that wouldn't work for React Native and doesn't let us
      // do the portal feature.
    } while (inst && inst.tag !== HostComponent);
    if (inst) {
      return inst;
    }
    return null;
  }
  
  /**
   * Return the lowest common ancestor of A and B, or null if they are in
   * different trees.
   */
  function getLowestCommonAncestor(instA: Fiber, instB: Fiber): Fiber | null {
    let nodeA = instA;
    let nodeB = instB;
    let depthA = 0;
    for (let tempA = nodeA; tempA; tempA = getParent(tempA)) {
      depthA++;
    }
    let depthB = 0;
    for (let tempB = nodeB; tempB; tempB = getParent(tempB)) {
      depthB++;
    }
  
    // If A is deeper, crawl up.
    while (depthA - depthB > 0) {
      nodeA = getParent(nodeA);
      depthA--;
    }
  
    // If B is deeper, crawl up.
    while (depthB - depthA > 0) {
      nodeB = getParent(nodeB);
      depthB--;
    }
  
    // Walk in lockstep until we find a match.
    let depth = depthA;
    while (depth--) {
      if (nodeA === nodeB || (nodeB !== null && nodeA === nodeB.alternate)) {
        return nodeA;
      }
      nodeA = getParent(nodeA);
      nodeB = getParent(nodeB);
    }
    return null;
  }
  
  function accumulateEnterLeaveListenersForEvent(
    dispatchQueue: DispatchQueue,
    event: KnownReactSyntheticEvent,
    target: Fiber,
    common: Fiber | null,
    inCapturePhase: boolean,
  ): void {
    const registrationName = event._reactName;
    const listeners: Array<DispatchListener> = [];
  
    let instance = target;
    while (instance !== null) {
      if (instance === common) {
        break;
      }
      const {alternate, stateNode, tag} = instance;
      if (alternate !== null && alternate === common) {
        break;
      }
      if (tag === HostComponent && stateNode !== null) {
        const currentTarget = stateNode;
        if (inCapturePhase) {
          const captureListener = getListener(instance, registrationName);
          if (captureListener != null) {
            listeners.unshift(
              createDispatchListener(instance, captureListener, currentTarget),
            );
          }
        } else if (!inCapturePhase) {
          const bubbleListener = getListener(instance, registrationName);
          if (bubbleListener != null) {
            listeners.push(
              createDispatchListener(instance, bubbleListener, currentTarget),
            );
          }
        }
      }
      instance = instance.return;
    }
    if (listeners.length !== 0) {
      dispatchQueue.push({event, listeners});
    }
  }
  
  // We should only use this function for:
  // - EnterLeaveEventPlugin
  // This is because we only process this plugin
  // in the bubble phase, so we need to accumulate two
  // phase event listeners.
  export function accumulateEnterLeaveTwoPhaseListeners(
    dispatchQueue: DispatchQueue,
    leaveEvent: KnownReactSyntheticEvent,
    enterEvent: null | KnownReactSyntheticEvent,
    from: Fiber | null,
    to: Fiber | null,
  ): void {
    const common = from && to ? getLowestCommonAncestor(from, to) : null;
  
    if (from !== null) {
      accumulateEnterLeaveListenersForEvent(
        dispatchQueue,
        leaveEvent,
        from,
        common,
        false,
      );
    }
    if (to !== null && enterEvent !== null) {
      accumulateEnterLeaveListenersForEvent(
        dispatchQueue,
        enterEvent,
        to,
        common,
        true,
      );
    }
  }
  /*
   收集所有监听该事件的 listener
  */
  export function accumulateEventHandleNonManagedNodeListeners(
    reactEventType: DOMEventName,
    currentTarget: EventTarget,
    inCapturePhase: boolean,
  ): Array<DispatchListener> {
    const listeners: Array<DispatchListener> = [];
  
    const eventListeners = getEventHandlerListeners(currentTarget);
    if (eventListeners !== null) {
      eventListeners.forEach(entry => {
        if (entry.type === reactEventType && entry.capture === inCapturePhase) {
          listeners.push(
            createDispatchListener(null, entry.callback, currentTarget),
          );
        }
      });
    }
    return listeners;
  }
  
  export function getListenerSetKey(
    domEventName: DOMEventName,
    capture: boolean,
  ): string {
    return `${domEventName}__${capture ? 'capture' : 'bubble'}`;
  }
  