React合成事件原理

date
Jan 28, 2022
slug
react-hechengshijian
status
Published
tags
React
源码
summary
从源码解析React合成事件原理
type
Post
React的事件系统采用了事件委托的思想,除了媒体类的事件无法被事务系统处理外,大部分事件都不会被绑定在具体元素上,而是统一绑定在document上。事件在dom节点上触发,冒泡到document上,document上的统一事件处理程序会将事件分发到具体的组件实例。分发前,react会对事件进行包装。

触发顺序

原生 -> 合成 -> document
本文基于React17.0.0
在React中,事件通过委托挂载到root(根DOM容器)上。但并不是所有的事件都是这样处理的,一些特殊情况会通过listenToNonDelegatedEvents直接绑定到触发DOM元素上,如scrollloadselectionchange等。这些事件除了监听的DOM不同,别的基本没区别。
React的合成事件,将原生事件与fiber链接了起来,从实现上分为:
  1. 监听原生事件: 将DOM元素和fiber对应
  1. 收集listeners: 遍历fiber tree, 收集所有监听本事件的listener函数.
  1. 派发合成事件: 构造合成事件, 遍历listeners进行派发.

合成事件初始化

SimpleEventPlugin.registerEvents();
EnterLeaveEventPlugin.registerEvents();
ChangeEventPlugin.registerEvents();
SelectEventPlugin.registerEvents();
BeforeInputEventPlugin.registerEvents();

事件注册

React在启动时,会调用createRootImpl
function createRootImpl(
  container: Container,
  tag: RootTag,
  options: void | RootOptions,
) {
  // ...省略
  listenToAllSupportedEvents(rootContainerElement);
  // ...省略
}
listenToAllSupportedEvents,实际上完成了事件的注册,
主要做了两件事:
  1. 防止重复监听的if优化
  1. 遍历allNativeEvents,调用listenToNativeEvent监听冒泡和捕获阶段的事件
export function listenToAllSupportedEvents(rootContainerElement: EventTarget) {

  if (rootContainerElement[listeningMarker]) {
    // 第一次是false
    return;
  }
  rootContainerElement[listeningMarker] = true;
  allNativeEvents.forEach(domEventName => {
    if (!nonDelegatedEvents.has(domEventName)) {
      listenToNativeEvent(
        domEventName,
        false, // false表示冒泡阶段的监听
        ((rootContainerElement: any): Element),
        null,
      );
    }
    listenToNativeEvent(
      domEventName,
      true, // true表示捕获阶段监听
      ((rootContainerElement: any): Element),
      null,
    );
  });
}
继续看listenToNativeEvent,内部利用了Set,保证了同样的事件只会被注册一次,通过调用addTrappedEventListener注册了事件监听
export function listenToNativeEvent(
  domEventName: DOMEventName,
  isCapturePhaseListener: boolean,
  rootContainerElement: EventTarget,
  targetElement: Element | null,
  eventSystemFlags?: EventSystemFlags = 0,
): void {
  let target = rootContainerElement;

  if (
    domEventName === 'selectionchange' &&
    (rootContainerElement: any).nodeType !== DOCUMENT_NODE
  ) {
    target = (rootContainerElement: any).ownerDocument;
  }

  if (
    targetElement !== null &&
    !isCapturePhaseListener &&
    nonDelegatedEvents.has(domEventName)
  ) {

    if (domEventName !== 'scroll') {
      return;
    }
    eventSystemFlags |= IS_NON_DELEGATED;
    target = targetElement;
  }
  // 给dom设置一个属性值(new Set()),如果已有则返回原先的
  const listenerSet = getEventListenerSet(target);
  // 根据domEventName和是否是捕获(isCapturePhaseListener)
  // 生成最终的eventName,如cancel__capture或cancel__bubble
  const listenerSetKey = getListenerSetKey(
    domEventName,
    isCapturePhaseListener,
  );

  if (!listenerSet.has(listenerSetKey)) {
    if (isCapturePhaseListener) {
      eventSystemFlags |= IS_CAPTURE_PHASE;
    }
    addTrappedEventListener(
      target,
      domEventName,
      eventSystemFlags,
      isCapturePhaseListener,
    );
    listenerSet.add(listenerSetKey);
  }

}
addTrappedEventListener首先会调用createEventListenerWrapperWithPriority根据事件的优先级创建监听器,之后会根据是冒泡或捕获,用调用对应的事件注册函数
function addTrappedEventListener(
  targetContainer: EventTarget,
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
  isCapturePhaseListener: boolean,
  isDeferredListenerForLegacyFBSupport?: boolean,
) {
  // 根据事件优先级创建事件监听器wrapper(bind)
  let listener = createEventListenerWrapperWithPriority(
    targetContainer,
    domEventName,
    eventSystemFlags,
  );
  let isPassiveListener = undefined;
  // passive用于浏览器优化页面滚动性能,除了在注册事件时多一个passive:true属性,没别的区别
  if (passiveBrowserEventsSupported) {
    if (
      domEventName === 'touchstart' ||
      domEventName === 'touchmove' ||
      domEventName === 'wheel'
    ) {
      isPassiveListener = true;
    }
  }
  let unsubscribeListener;
  // 注册事件监听
  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 addEventCaptureListener(
  target: EventTarget,
  eventType: string,
  listener: Function,
): Function {
  target.addEventListener(eventType, listener, true);
  return listener;
}

export function addEventBubbleListener(
  target: EventTarget,
  eventType: string,
  listener: Function,
): Function {
  target.addEventListener(eventType, listener, false);
  return listener;
}
createEventListenerWrapperWithPriority中,会根据DOM事件名拿到事件的优先级的高低(离散,用户阻塞,连续),来调用不同的listenerWrapper
  • DiscreteEvent: click, keydown, input 对应dispatchDiscreteEvent
  • UserBlockingEvent: drag, scroll 对应dispatchUserBlockingUpdate
  • ContinuousEvent: play, load 对应 dispatchEvent
export function createEventListenerWrapperWithPriority(
  targetContainer: EventTarget,
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
): Function {
  // 根据dom事件名获取时间优先级
  const eventPriority = getEventPriorityForPluginSystem(domEventName);
  let listenerWrapper;
  switch (eventPriority) {
    // 0:离散事件,如click,优先级低
    case DiscreteEvent:
      listenerWrapper = dispatchDiscreteEvent;
      break;
    // 1: 用户阻塞事件,如scroll,优先级中
    case UserBlockingEvent:
      listenerWrapper = dispatchUserBlockingUpdate;
      break;
    // 2:连续事件,如load, 优先级高
    case ContinuousEvent:
    default:
      listenerWrapper = dispatchEvent;
      break;
  }

  return listenerWrapper.bind(
    null,
    domEventName,
    eventSystemFlags,
    targetContainer,
  );
}

事件触发

当原生事件被触发后,进入dispatchEvent,整个链路就长了。
export function dispatchEvent(
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
  targetContainer: EventTarget,
  nativeEvent: AnyNativeEvent,
): void {
  if (!_enabled) {
    return;
  }
  let allowReplay = true;
  allowReplay = (eventSystemFlags & IS_CAPTURE_PHASE) === 0;
  if (
    allowReplay &&
    hasQueuedDiscreteEvents() &&
    isReplayableDiscreteEvent(domEventName)
  ) {
    queueDiscreteEvent(
      null, // Flags that we're not actually blocked on anything as far as we know.
      domEventName,
      eventSystemFlags,
      targetContainer,
      nativeEvent,
    );
    return;
  }

  const blockedOn = attemptToDispatchEvent(
    domEventName,
    eventSystemFlags,
    targetContainer,
    nativeEvent,
  );

  if (blockedOn === null) {
    // We successfully dispatched this event.
    if (allowReplay) {
      clearIfContinuousEvent(domEventName, nativeEvent);
    }
    return;
  }

  if (allowReplay) {
    if (isReplayableDiscreteEvent(domEventName)) {
      queueDiscreteEvent(
        blockedOn,
        domEventName,
        eventSystemFlags,
        targetContainer,
        nativeEvent,
      );
      return;
    }
    if (
      queueIfContinuousEvent(
        blockedOn,
        domEventName,
        eventSystemFlags,
        targetContainer,
        nativeEvent,
      )
    ) {
      return;
    }
    clearIfContinuousEvent(domEventName, nativeEvent);
  }
  dispatchEventForPluginEventSystem(
    domEventName,
    eventSystemFlags,
    nativeEvent,
    null,
    targetContainer,
  );
}
首先进入attemptToDispatchEvent,主要做的是拿到原生DOM节点,和该节点对应的fiber,调用dispatchEventForPluginEventSystem通过插件系统,触发事件
export function attemptToDispatchEvent(
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
  targetContainer: EventTarget,
  nativeEvent: AnyNativeEvent,
): null | Container | SuspenseInstance {
  // TODO: Warn if _enabled is false.
  // 获取原生dom节点,一般是nativeEvent.target
  const nativeEventTarget = getEventTarget(nativeEvent);
  // 获取原生dom节点对应的fiber
  let targetInst = getClosestInstanceFromNode(nativeEventTarget);

  if (targetInst !== null) {
    const nearestMounted = getNearestMountedFiber(targetInst);
    // tree已经被卸载
    if (nearestMounted === null) {
      targetInst = null;
    } else {
      const tag = nearestMounted.tag;
      if (tag === SuspenseComponent) {
        const instance = getSuspenseInstanceFromFiber(nearestMounted);
        if (instance !== null) {
          return instance;
        }
        targetInst = null;
      } else if (tag === HostRoot) {
        const root: FiberRoot = nearestMounted.stateNode;
        if (root.hydrate) {

          return getContainerFromFiber(nearestMounted);
        }
        targetInst = null;
      } else if (nearestMounted !== targetInst) {

        targetInst = null;
      }
    }
  }
  dispatchEventForPluginEventSystem(
    domEventName,
    eventSystemFlags,
    nativeEvent,
    targetInst,
    targetContainer,
  );
  // We're not blocked on anything.
  return null;
}
dispatchEventForPluginEventSystem内部,通过一个向上的循环,找到当前rootContainerfiber,进入dispatchEventsForPlugins
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 && // IS_EVENT_HANDLE_NON_MANAGED_NODE = 1
    (eventSystemFlags & IS_NON_DELEGATED) === 0 // IS_NON_DELEGATED = 2
  ) {
    const targetContainerNode = ((targetContainer: any): Node);
    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;
          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) {
              node = ancestorInst = parentNode;
              continue mainLoop;
            }
            container = container.parentNode;
          }
        }
        node = node.return;
      }
    }
  }

  batchedEventUpdates(() =>
    dispatchEventsForPlugins(
      domEventName,
      eventSystemFlags,
      nativeEvent,
      ancestorInst,
      targetContainer,
    ),
  );
}
dispatchEventsForPlugins内部,会调用extractEvents,对事件对象进行合成,收集事件到执行路径上,并调用processDispatchQueue执行收集到组件中真正的事件
function dispatchEventsForPlugins(
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
  nativeEvent: AnyNativeEvent,
  targetInst: null | Fiber,
  targetContainer: EventTarget,
): void {
  // 一般是nativeEvent.target
  const nativeEventTarget = getEventTarget(nativeEvent);
  const dispatchQueue: DispatchQueue = [];
  // 事件对象的合成,收集事件到执行路径上
  extractEvents(
    dispatchQueue,
    domEventName,
    targetInst,
    nativeEvent,
    nativeEventTarget,
    eventSystemFlags,
    targetContainer,
  );
  // 执行收集到的组件中真正的事件
  processDispatchQueue(dispatchQueue, eventSystemFlags);
}
由于其余EventPlugin都基于SimpleEventPlugin封装,此处只对SimpleEventPlugin展开
function extractEvents(
  dispatchQueue: DispatchQueue,
  domEventName: DOMEventName,
  targetInst: null | Fiber,
  nativeEvent: AnyNativeEvent,
  nativeEventTarget: null | EventTarget,
  eventSystemFlags: EventSystemFlags,
  targetContainer: EventTarget,
): void {
  // 获取domEventName对应的ReactEventName,如click => onClick
  const reactName = topLevelEventsToReactNames.get(domEventName);
  if (reactName === undefined) {
    return;
  }
  let SyntheticEventCtor = SyntheticEvent;
  let reactEventType: string = domEventName;

  // ...省略 根据domEventName,将SyntheticEventCtor赋值为不同的事件类型

  // IS_CAPTURE_PHASE = 1 << 2 = 0b0100
  const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
  // scroll事件不冒泡
  const accumulateTargetOnly =
    !inCapturePhase &&
    domEventName === 'scroll';
  // 事件对象分发 & 收集事件
  const listeners = accumulateSinglePhaseListeners(
    targetInst,
    reactName,
    nativeEvent.type,
    inCapturePhase,
    accumulateTargetOnly,
  );
  if (listeners.length > 0) {
    // 构造合成事件
    const event = new SyntheticEventCtor(
      reactName,
      reactEventType,
      null,
      nativeEvent,
      nativeEventTarget,
    );
    dispatchQueue.push({event, listeners});
  }
}
accumulateSinglePhaseListeners中,会收集fiber中所有listener的回调。完成后,会构造React合成事件,并将其推入dispatchQueue派发队列中。
export function accumulateSinglePhaseListeners(
  targetFiber: Fiber | null,
  reactName: string | null,
  nativeEventType: string,
  inCapturePhase: boolean,
  accumulateTargetOnly: boolean,
): Array<DispatchListener> {
  // 根据事件名来识别是冒泡阶段的事件还是捕获阶段的事件
  const captureName = reactName !== null ? reactName + 'Capture' : null;
  const reactEventName = inCapturePhase ? captureName : reactName;
  // 声明存放事件监听的数组
  const listeners: Array<DispatchListener> = [];
  // 找到目标元素
  let instance = targetFiber;
  let lastHostComponent = null;
  // 从目标元素开始一直到root,累加所有的fiber对象和事件监听。
  while (instance !== null) {
    const {stateNode, tag} = instance;
    if (tag === HostComponent && stateNode !== null) {
      lastHostComponent = stateNode;
      // Standard React on* listeners, i.e. onClick or onClickCapture
      if (reactEventName !== null) {
        // instance.stateNode上有pendingProps,如onClick
        const listener = getListener(instance, reactEventName);
        if (listener != null) {
          listeners.push(
            createDispatchListener(instance, listener, lastHostComponent),
          );
        }
      }
    }
    // 如果只收集目标节点的话,那么就不用再往上收集了,直接跳出
    if (accumulateTargetOnly) {
      break;
    }
    instance = instance.return;
  }
  return listeners;
}
对事件对象进行合成,收集事件到执行路径上完成后,走到processDiaptchQueue,执行收集到组件中真正的事件
export function processDispatchQueue(
  dispatchQueue: DispatchQueue,
  eventSystemFlags: EventSystemFlags,
): void {
  // 是否是捕获阶段
  const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
  for (let i = 0; i < dispatchQueue.length; i++) {
    // 从dispatchQueue中取出事件对象和事件监听数组
    const {event, listeners} = dispatchQueue[i];
    // 将事件监听交由processDispatchQueueItemsInOrder去触发,同时传入事件对象供事件监听使用
    processDispatchQueueItemsInOrder(event, listeners, inCapturePhase);
  }
  rethrowCaughtError();
}
processDispatchQueueItemsInOrder内部,会根据是冒泡或捕获,正序或倒序遍历listeners(不难理解,收集diapstchListeners时,是从当前fiber遍历至root,所以正序遍历是冒泡的顺序),依次调用executeDispatch派发事件
function processDispatchQueueItemsInOrder(
  event: ReactSyntheticEvent,
  dispatchListeners: Array<DispatchListener>,
  inCapturePhase: boolean,
): void {
  let previousInstance;
  if (inCapturePhase) {
    // 事件捕获倒序循环
    for (let i = dispatchListeners.length - 1; i >= 0; i--) {
      const {instance, currentTarget, listener} = dispatchListeners[i];
      // onClick = e => e.stopPropagation()后
      // event的isPropagationStopped = functionThatReturnsTrue = () => true
      // 那么就不会在调用后面的listener了,下面的冒泡同理
      if (instance !== previousInstance && event.isPropagationStopped()) {
        return;
      }
      executeDispatch(event, listener, currentTarget);
      previousInstance = instance;
    }
  } else {
    // 事件冒泡正序循环
    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;
    }
  }
}
executeDispatch内部很简单,将currentTarget执行真正的事件函数后,再讲event.currentTarget重置为null
function executeDispatch(
  event: ReactSyntheticEvent,
  listener: Function,
  currentTarget: EventTarget,
): void {
  const type = event.type || 'unknown-event';
  // 将currentTarget,运行真正的事件函数后,如onClick后,再将event.currentTarget置为null
  event.currentTarget = currentTarget;
  invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event);
  event.currentTarget = null;
}

绑定事件

  1. diff时,通过registrationNameModule判断props是否有事件类型
props: {
	className: "btn-primary",
  onClick: function ..., // 事件类型
  onBlur: function ... //事件类型
}
// registrationNameModule
{
    onBlur: SimpleEventPlugin,
    onClick: SimpleEventPlugin,
    onClickCapture: SimpleEventPlugin,
    onChange: ChangeEventPlugin,
    onChangeCapture: ChangeEventPlugin,
    onMouseEnter: EnterLeaveEventPlugin,
    onMouseLeave: EnterLeaveEventPlugin,
    ...
}
  1. 通过registrationNameDependencies检查事件依赖了哪些原生事件
// registrationNameDependencies
{
    onBlur: ['blur'],
    onClick: ['click'],
    onClickCapture: ['click'],
    onChange: ['blur', 'change', 'click', 'focus', 'input', 'keydown', 'keyup', 'selectionchange'],
    onMouseEnter: ['mouseout', 'mouseover'],
    onMouseLeave: ['mouseout', 'mouseover'],
    ...
}
  1. 检查这个事件是否注册过,如果没有,就挂在document上,将React提供的dispatchEvent回调

触发事件

  1. 任意一个事件触发,执行 dispatchEvent 函数。
  1. dispatchEvent 执行 batchedEventUpdates(handleTopLevel)batchedEventUpdates 会打开批量渲染开关并调用 handleTopLevel
  1. handleTopLevel 会依次执行 plugins 里所有的事件插件。
  1. 如果一个插件检测到自己需要处理的事件类型时,则处理该事件。

  1. React 的合成事件只能在事件周期内使用,因为这个对象很可能被其他阶段复用, 如果想持久化需要手动调用event.persist() 告诉 React 这个对象需要持久化。( React17 中被废弃)
  1. React 的冒泡和捕获并不是真正 DOM 级别的冒泡和捕获
  1. React 会在一个原生事件里触发所有相关节点的 onClick 事件, 在执行这些onClick之前 React 会打开批量渲染开关,这个开关会将所有的setState变成异步函数。
  • 事件只针对原生组件生效,自定义组件不会触发 onClick
const handleClick = e => {
	setTimeout(()=>{
  	console.log(e.target.value)
  },100)
} //错误,因为事件处理完后已经销毁

// React在派发事件时会批量更新,所有的setState都会变成异步的
// 1,2 异步,render1次 3,4同步,render2次
const handleClick = () => {
	setState({num: 1});
  setState({num: 2});
  setTimeout(()=>{
  	setState({num: 3});
    setState({num: 4});
  },100)
}

React17

调整将顶层事件绑定到container上,而不是document上
本文基于React17.0.0
在React中,事件通过委托挂载到root(根DOM容器)上。但并不是所有的事件都是这样处理的,一些特殊情况会通过listenToNonDelegatedEvents直接绑定到触发DOM元素上,如scrollloadselectionchange等。这些事件除了监听的DOM不同,别的基本没区别。
React的合成事件,将原生事件与fiber链接了起来,从实现上分为:
  1. 监听原生事件: 将DOM元素和fiber对应
  1. 收集listeners: 遍历fiber tree, 收集所有监听本事件的listener函数.
  1. 派发合成事件: 构造合成事件, 遍历listeners进行派发.

合成事件初始化

SimpleEventPlugin.registerEvents();
EnterLeaveEventPlugin.registerEvents();
ChangeEventPlugin.registerEvents();
SelectEventPlugin.registerEvents();
BeforeInputEventPlugin.registerEvents();

事件注册

React在启动时,会调用createRootImpl
function createRootImpl(
  container: Container,
  tag: RootTag,
  options: void | RootOptions,
) {
  // ...省略
  listenToAllSupportedEvents(rootContainerElement);
  // ...省略
}
listenToAllSupportedEvents,实际上完成了事件的注册,
主要做了两件事:
  1. 防止重复监听的if优化
  1. 遍历allNativeEvents,调用listenToNativeEvent监听冒泡和捕获阶段的事件
export function listenToAllSupportedEvents(rootContainerElement: EventTarget) {

  if (rootContainerElement[listeningMarker]) {
    // 第一次是false
    return;
  }
  rootContainerElement[listeningMarker] = true;
  allNativeEvents.forEach(domEventName => {
    if (!nonDelegatedEvents.has(domEventName)) {
      listenToNativeEvent(
        domEventName,
        false, // false表示冒泡阶段的监听
        ((rootContainerElement: any): Element),
        null,
      );
    }
    listenToNativeEvent(
      domEventName,
      true, // true表示捕获阶段监听
      ((rootContainerElement: any): Element),
      null,
    );
  });
}
继续看listenToNativeEvent,内部利用了Set,保证了同样的事件只会被注册一次,通过调用addTrappedEventListener注册了事件监听
export function listenToNativeEvent(
  domEventName: DOMEventName,
  isCapturePhaseListener: boolean,
  rootContainerElement: EventTarget,
  targetElement: Element | null,
  eventSystemFlags?: EventSystemFlags = 0,
): void {
  let target = rootContainerElement;

  if (
    domEventName === 'selectionchange' &&
    (rootContainerElement: any).nodeType !== DOCUMENT_NODE
  ) {
    target = (rootContainerElement: any).ownerDocument;
  }

  if (
    targetElement !== null &&
    !isCapturePhaseListener &&
    nonDelegatedEvents.has(domEventName)
  ) {

    if (domEventName !== 'scroll') {
      return;
    }
    eventSystemFlags |= IS_NON_DELEGATED;
    target = targetElement;
  }
  // 给dom设置一个属性值(new Set()),如果已有则返回原先的
  const listenerSet = getEventListenerSet(target);
  // 根据domEventName和是否是捕获(isCapturePhaseListener)
  // 生成最终的eventName,如cancel__capture或cancel__bubble
  const listenerSetKey = getListenerSetKey(
    domEventName,
    isCapturePhaseListener,
  );

  if (!listenerSet.has(listenerSetKey)) {
    if (isCapturePhaseListener) {
      eventSystemFlags |= IS_CAPTURE_PHASE;
    }
    addTrappedEventListener(
      target,
      domEventName,
      eventSystemFlags,
      isCapturePhaseListener,
    );
    listenerSet.add(listenerSetKey);
  }

}
addTrappedEventListener首先会调用createEventListenerWrapperWithPriority根据事件的优先级创建监听器,之后会根据是冒泡或捕获,用调用对应的事件注册函数
function addTrappedEventListener(
  targetContainer: EventTarget,
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
  isCapturePhaseListener: boolean,
  isDeferredListenerForLegacyFBSupport?: boolean,
) {
  // 根据事件优先级创建事件监听器wrapper(bind)
  let listener = createEventListenerWrapperWithPriority(
    targetContainer,
    domEventName,
    eventSystemFlags,
  );
  let isPassiveListener = undefined;
  // passive用于浏览器优化页面滚动性能,除了在注册事件时多一个passive:true属性,没别的区别
  if (passiveBrowserEventsSupported) {
    if (
      domEventName === 'touchstart' ||
      domEventName === 'touchmove' ||
      domEventName === 'wheel'
    ) {
      isPassiveListener = true;
    }
  }
  let unsubscribeListener;
  // 注册事件监听
  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 addEventCaptureListener(
  target: EventTarget,
  eventType: string,
  listener: Function,
): Function {
  target.addEventListener(eventType, listener, true);
  return listener;
}

export function addEventBubbleListener(
  target: EventTarget,
  eventType: string,
  listener: Function,
): Function {
  target.addEventListener(eventType, listener, false);
  return listener;
}
createEventListenerWrapperWithPriority中,会根据DOM事件名拿到事件的优先级的高低(离散,用户阻塞,连续),来调用不同的listenerWrapper
  • DiscreteEvent: click, keydown, input 对应dispatchDiscreteEvent
  • UserBlockingEvent: drag, scroll 对应dispatchUserBlockingUpdate
  • ContinuousEvent: play, load 对应 dispatchEvent
export function createEventListenerWrapperWithPriority(
  targetContainer: EventTarget,
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
): Function {
  // 根据dom事件名获取时间优先级
  const eventPriority = getEventPriorityForPluginSystem(domEventName);
  let listenerWrapper;
  switch (eventPriority) {
    // 0:离散事件,如click,优先级低
    case DiscreteEvent:
      listenerWrapper = dispatchDiscreteEvent;
      break;
    // 1: 用户阻塞事件,如scroll,优先级中
    case UserBlockingEvent:
      listenerWrapper = dispatchUserBlockingUpdate;
      break;
    // 2:连续事件,如load, 优先级高
    case ContinuousEvent:
    default:
      listenerWrapper = dispatchEvent;
      break;
  }

  return listenerWrapper.bind(
    null,
    domEventName,
    eventSystemFlags,
    targetContainer,
  );
}

事件触发

当原生事件被触发后,进入dispatchEvent,整个链路就长了。
export function dispatchEvent(
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
  targetContainer: EventTarget,
  nativeEvent: AnyNativeEvent,
): void {
  if (!_enabled) {
    return;
  }
  let allowReplay = true;
  allowReplay = (eventSystemFlags & IS_CAPTURE_PHASE) === 0;
  if (
    allowReplay &&
    hasQueuedDiscreteEvents() &&
    isReplayableDiscreteEvent(domEventName)
  ) {
    queueDiscreteEvent(
      null, // Flags that we're not actually blocked on anything as far as we know.
      domEventName,
      eventSystemFlags,
      targetContainer,
      nativeEvent,
    );
    return;
  }

  const blockedOn = attemptToDispatchEvent(
    domEventName,
    eventSystemFlags,
    targetContainer,
    nativeEvent,
  );

  if (blockedOn === null) {
    // We successfully dispatched this event.
    if (allowReplay) {
      clearIfContinuousEvent(domEventName, nativeEvent);
    }
    return;
  }

  if (allowReplay) {
    if (isReplayableDiscreteEvent(domEventName)) {
      queueDiscreteEvent(
        blockedOn,
        domEventName,
        eventSystemFlags,
        targetContainer,
        nativeEvent,
      );
      return;
    }
    if (
      queueIfContinuousEvent(
        blockedOn,
        domEventName,
        eventSystemFlags,
        targetContainer,
        nativeEvent,
      )
    ) {
      return;
    }
    clearIfContinuousEvent(domEventName, nativeEvent);
  }
  dispatchEventForPluginEventSystem(
    domEventName,
    eventSystemFlags,
    nativeEvent,
    null,
    targetContainer,
  );
}
首先进入attemptToDispatchEvent,主要做的是拿到原生DOM节点,和该节点对应的fiber,调用dispatchEventForPluginEventSystem通过插件系统,触发事件
export function attemptToDispatchEvent(
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
  targetContainer: EventTarget,
  nativeEvent: AnyNativeEvent,
): null | Container | SuspenseInstance {
  // TODO: Warn if _enabled is false.
  // 获取原生dom节点,一般是nativeEvent.target
  const nativeEventTarget = getEventTarget(nativeEvent);
  // 获取原生dom节点对应的fiber
  let targetInst = getClosestInstanceFromNode(nativeEventTarget);

  if (targetInst !== null) {
    const nearestMounted = getNearestMountedFiber(targetInst);
    // tree已经被卸载
    if (nearestMounted === null) {
      targetInst = null;
    } else {
      const tag = nearestMounted.tag;
      if (tag === SuspenseComponent) {
        const instance = getSuspenseInstanceFromFiber(nearestMounted);
        if (instance !== null) {
          return instance;
        }
        targetInst = null;
      } else if (tag === HostRoot) {
        const root: FiberRoot = nearestMounted.stateNode;
        if (root.hydrate) {

          return getContainerFromFiber(nearestMounted);
        }
        targetInst = null;
      } else if (nearestMounted !== targetInst) {

        targetInst = null;
      }
    }
  }
  dispatchEventForPluginEventSystem(
    domEventName,
    eventSystemFlags,
    nativeEvent,
    targetInst,
    targetContainer,
  );
  // We're not blocked on anything.
  return null;
}
dispatchEventForPluginEventSystem内部,通过一个向上的循环,找到当前rootContainerfiber,进入dispatchEventsForPlugins
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 && // IS_EVENT_HANDLE_NON_MANAGED_NODE = 1
    (eventSystemFlags & IS_NON_DELEGATED) === 0 // IS_NON_DELEGATED = 2
  ) {
    const targetContainerNode = ((targetContainer: any): Node);
    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;
          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) {
              node = ancestorInst = parentNode;
              continue mainLoop;
            }
            container = container.parentNode;
          }
        }
        node = node.return;
      }
    }
  }

  batchedEventUpdates(() =>
    dispatchEventsForPlugins(
      domEventName,
      eventSystemFlags,
      nativeEvent,
      ancestorInst,
      targetContainer,
    ),
  );
}
dispatchEventsForPlugins内部,会调用extractEvents,对事件对象进行合成,收集事件到执行路径上,并调用processDispatchQueue执行收集到组件中真正的事件
function dispatchEventsForPlugins(
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
  nativeEvent: AnyNativeEvent,
  targetInst: null | Fiber,
  targetContainer: EventTarget,
): void {
  // 一般是nativeEvent.target
  const nativeEventTarget = getEventTarget(nativeEvent);
  const dispatchQueue: DispatchQueue = [];
  // 事件对象的合成,收集事件到执行路径上
  extractEvents(
    dispatchQueue,
    domEventName,
    targetInst,
    nativeEvent,
    nativeEventTarget,
    eventSystemFlags,
    targetContainer,
  );
  // 执行收集到的组件中真正的事件
  processDispatchQueue(dispatchQueue, eventSystemFlags);
}
由于其余EventPlugin都基于SimpleEventPlugin封装,此处只对SimpleEventPlugin展开
function extractEvents(
  dispatchQueue: DispatchQueue,
  domEventName: DOMEventName,
  targetInst: null | Fiber,
  nativeEvent: AnyNativeEvent,
  nativeEventTarget: null | EventTarget,
  eventSystemFlags: EventSystemFlags,
  targetContainer: EventTarget,
): void {
  // 获取domEventName对应的ReactEventName,如click => onClick
  const reactName = topLevelEventsToReactNames.get(domEventName);
  if (reactName === undefined) {
    return;
  }
  let SyntheticEventCtor = SyntheticEvent;
  let reactEventType: string = domEventName;

  // ...省略 根据domEventName,将SyntheticEventCtor赋值为不同的事件类型

  // IS_CAPTURE_PHASE = 1 << 2 = 0b0100
  const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
  // scroll事件不冒泡
  const accumulateTargetOnly =
    !inCapturePhase &&
    domEventName === 'scroll';
  // 事件对象分发 & 收集事件
  const listeners = accumulateSinglePhaseListeners(
    targetInst,
    reactName,
    nativeEvent.type,
    inCapturePhase,
    accumulateTargetOnly,
  );
  if (listeners.length > 0) {
    // 构造合成事件
    const event = new SyntheticEventCtor(
      reactName,
      reactEventType,
      null,
      nativeEvent,
      nativeEventTarget,
    );
    dispatchQueue.push({event, listeners});
  }
}
accumulateSinglePhaseListeners中,会收集fiber中所有listener的回调。完成后,会构造React合成事件,并将其推入dispatchQueue派发队列中。
export function accumulateSinglePhaseListeners(
  targetFiber: Fiber | null,
  reactName: string | null,
  nativeEventType: string,
  inCapturePhase: boolean,
  accumulateTargetOnly: boolean,
): Array<DispatchListener> {
  // 根据事件名来识别是冒泡阶段的事件还是捕获阶段的事件
  const captureName = reactName !== null ? reactName + 'Capture' : null;
  const reactEventName = inCapturePhase ? captureName : reactName;
  // 声明存放事件监听的数组
  const listeners: Array<DispatchListener> = [];
  // 找到目标元素
  let instance = targetFiber;
  let lastHostComponent = null;
  // 从目标元素开始一直到root,累加所有的fiber对象和事件监听。
  while (instance !== null) {
    const {stateNode, tag} = instance;
    if (tag === HostComponent && stateNode !== null) {
      lastHostComponent = stateNode;
      // Standard React on* listeners, i.e. onClick or onClickCapture
      if (reactEventName !== null) {
        // instance.stateNode上有pendingProps,如onClick
        const listener = getListener(instance, reactEventName);
        if (listener != null) {
          listeners.push(
            createDispatchListener(instance, listener, lastHostComponent),
          );
        }
      }
    }
    // 如果只收集目标节点的话,那么就不用再往上收集了,直接跳出
    if (accumulateTargetOnly) {
      break;
    }
    instance = instance.return;
  }
  return listeners;
}
对事件对象进行合成,收集事件到执行路径上完成后,走到processDiaptchQueue,执行收集到组件中真正的事件
export function processDispatchQueue(
  dispatchQueue: DispatchQueue,
  eventSystemFlags: EventSystemFlags,
): void {
  // 是否是捕获阶段
  const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
  for (let i = 0; i < dispatchQueue.length; i++) {
    // 从dispatchQueue中取出事件对象和事件监听数组
    const {event, listeners} = dispatchQueue[i];
    // 将事件监听交由processDispatchQueueItemsInOrder去触发,同时传入事件对象供事件监听使用
    processDispatchQueueItemsInOrder(event, listeners, inCapturePhase);
  }
  rethrowCaughtError();
}
processDispatchQueueItemsInOrder内部,会根据是冒泡或捕获,正序或倒序遍历listeners(不难理解,收集diapstchListeners时,是从当前fiber遍历至root,所以正序遍历是冒泡的顺序),依次调用executeDispatch派发事件
function processDispatchQueueItemsInOrder(
  event: ReactSyntheticEvent,
  dispatchListeners: Array<DispatchListener>,
  inCapturePhase: boolean,
): void {
  let previousInstance;
  if (inCapturePhase) {
    // 事件捕获倒序循环
    for (let i = dispatchListeners.length - 1; i >= 0; i--) {
      const {instance, currentTarget, listener} = dispatchListeners[i];
      // onClick = e => e.stopPropagation()后
      // event的isPropagationStopped = functionThatReturnsTrue = () => true
      // 那么就不会在调用后面的listener了,下面的冒泡同理
      if (instance !== previousInstance && event.isPropagationStopped()) {
        return;
      }
      executeDispatch(event, listener, currentTarget);
      previousInstance = instance;
    }
  } else {
    // 事件冒泡正序循环
    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;
    }
  }
}
executeDispatch内部很简单,将currentTarget执行真正的事件函数后,再讲event.currentTarget重置为null
function executeDispatch(
  event: ReactSyntheticEvent,
  listener: Function,
  currentTarget: EventTarget,
): void {
  const type = event.type || 'unknown-event';
  // 将currentTarget,运行真正的事件函数后,如onClick后,再将event.currentTarget置为null
  event.currentTarget = currentTarget;
  invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event);
  event.currentTarget = null;
}
本文基于React17.0.0
在React中,事件通过委托挂载到root(根DOM容器)上。但并不是所有的事件都是这样处理的,一些特殊情况会通过listenToNonDelegatedEvents直接绑定到触发DOM元素上,如scrollloadselectionchange等。这些事件除了监听的DOM不同,别的基本没区别。
React的合成事件,将原生事件与fiber链接了起来,从实现上分为:
  1. 监听原生事件: 将DOM元素和fiber对应
  1. 收集listeners: 遍历fiber tree, 收集所有监听本事件的listener函数.
  1. 派发合成事件: 构造合成事件, 遍历listeners进行派发.

合成事件初始化

SimpleEventPlugin.registerEvents();
EnterLeaveEventPlugin.registerEvents();
ChangeEventPlugin.registerEvents();
SelectEventPlugin.registerEvents();
BeforeInputEventPlugin.registerEvents();

事件注册

React在启动时,会调用createRootImpl
function createRootImpl(
  container: Container,
  tag: RootTag,
  options: void | RootOptions,
) {
  // ...省略
  listenToAllSupportedEvents(rootContainerElement);
  // ...省略
}
listenToAllSupportedEvents,实际上完成了事件的注册,
主要做了两件事:
  1. 防止重复监听的if优化
  1. 遍历allNativeEvents,调用listenToNativeEvent监听冒泡和捕获阶段的事件
export function listenToAllSupportedEvents(rootContainerElement: EventTarget) {

  if (rootContainerElement[listeningMarker]) {
    // 第一次是false
    return;
  }
  rootContainerElement[listeningMarker] = true;
  allNativeEvents.forEach(domEventName => {
    if (!nonDelegatedEvents.has(domEventName)) {
      listenToNativeEvent(
        domEventName,
        false, // false表示冒泡阶段的监听
        ((rootContainerElement: any): Element),
        null,
      );
    }
    listenToNativeEvent(
      domEventName,
      true, // true表示捕获阶段监听
      ((rootContainerElement: any): Element),
      null,
    );
  });
}
继续看listenToNativeEvent,内部利用了Set,保证了同样的事件只会被注册一次,通过调用addTrappedEventListener注册了事件监听
export function listenToNativeEvent(
  domEventName: DOMEventName,
  isCapturePhaseListener: boolean,
  rootContainerElement: EventTarget,
  targetElement: Element | null,
  eventSystemFlags?: EventSystemFlags = 0,
): void {
  let target = rootContainerElement;

  if (
    domEventName === 'selectionchange' &&
    (rootContainerElement: any).nodeType !== DOCUMENT_NODE
  ) {
    target = (rootContainerElement: any).ownerDocument;
  }

  if (
    targetElement !== null &&
    !isCapturePhaseListener &&
    nonDelegatedEvents.has(domEventName)
  ) {

    if (domEventName !== 'scroll') {
      return;
    }
    eventSystemFlags |= IS_NON_DELEGATED;
    target = targetElement;
  }
  // 给dom设置一个属性值(new Set()),如果已有则返回原先的
  const listenerSet = getEventListenerSet(target);
  // 根据domEventName和是否是捕获(isCapturePhaseListener)
  // 生成最终的eventName,如cancel__capture或cancel__bubble
  const listenerSetKey = getListenerSetKey(
    domEventName,
    isCapturePhaseListener,
  );

  if (!listenerSet.has(listenerSetKey)) {
    if (isCapturePhaseListener) {
      eventSystemFlags |= IS_CAPTURE_PHASE;
    }
    addTrappedEventListener(
      target,
      domEventName,
      eventSystemFlags,
      isCapturePhaseListener,
    );
    listenerSet.add(listenerSetKey);
  }

}
addTrappedEventListener首先会调用createEventListenerWrapperWithPriority根据事件的优先级创建监听器,之后会根据是冒泡或捕获,用调用对应的事件注册函数
function addTrappedEventListener(
  targetContainer: EventTarget,
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
  isCapturePhaseListener: boolean,
  isDeferredListenerForLegacyFBSupport?: boolean,
) {
  // 根据事件优先级创建事件监听器wrapper(bind)
  let listener = createEventListenerWrapperWithPriority(
    targetContainer,
    domEventName,
    eventSystemFlags,
  );
  let isPassiveListener = undefined;
  // passive用于浏览器优化页面滚动性能,除了在注册事件时多一个passive:true属性,没别的区别
  if (passiveBrowserEventsSupported) {
    if (
      domEventName === 'touchstart' ||
      domEventName === 'touchmove' ||
      domEventName === 'wheel'
    ) {
      isPassiveListener = true;
    }
  }
  let unsubscribeListener;
  // 注册事件监听
  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 addEventCaptureListener(
  target: EventTarget,
  eventType: string,
  listener: Function,
): Function {
  target.addEventListener(eventType, listener, true);
  return listener;
}

export function addEventBubbleListener(
  target: EventTarget,
  eventType: string,
  listener: Function,
): Function {
  target.addEventListener(eventType, listener, false);
  return listener;
}
createEventListenerWrapperWithPriority中,会根据DOM事件名拿到事件的优先级的高低(离散,用户阻塞,连续),来调用不同的listenerWrapper
  • DiscreteEvent: click, keydown, input 对应dispatchDiscreteEvent
  • UserBlockingEvent: drag, scroll 对应dispatchUserBlockingUpdate
  • ContinuousEvent: play, load 对应 dispatchEvent
export function createEventListenerWrapperWithPriority(
  targetContainer: EventTarget,
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
): Function {
  // 根据dom事件名获取时间优先级
  const eventPriority = getEventPriorityForPluginSystem(domEventName);
  let listenerWrapper;
  switch (eventPriority) {
    // 0:离散事件,如click,优先级低
    case DiscreteEvent:
      listenerWrapper = dispatchDiscreteEvent;
      break;
    // 1: 用户阻塞事件,如scroll,优先级中
    case UserBlockingEvent:
      listenerWrapper = dispatchUserBlockingUpdate;
      break;
    // 2:连续事件,如load, 优先级高
    case ContinuousEvent:
    default:
      listenerWrapper = dispatchEvent;
      break;
  }

  return listenerWrapper.bind(
    null,
    domEventName,
    eventSystemFlags,
    targetContainer,
  );
}

事件触发

当原生事件被触发后,进入dispatchEvent,整个链路就长了。
export function dispatchEvent(
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
  targetContainer: EventTarget,
  nativeEvent: AnyNativeEvent,
): void {
  if (!_enabled) {
    return;
  }
  let allowReplay = true;
  allowReplay = (eventSystemFlags & IS_CAPTURE_PHASE) === 0;
  if (
    allowReplay &&
    hasQueuedDiscreteEvents() &&
    isReplayableDiscreteEvent(domEventName)
  ) {
    queueDiscreteEvent(
      null, // Flags that we're not actually blocked on anything as far as we know.
      domEventName,
      eventSystemFlags,
      targetContainer,
      nativeEvent,
    );
    return;
  }

  const blockedOn = attemptToDispatchEvent(
    domEventName,
    eventSystemFlags,
    targetContainer,
    nativeEvent,
  );

  if (blockedOn === null) {
    // We successfully dispatched this event.
    if (allowReplay) {
      clearIfContinuousEvent(domEventName, nativeEvent);
    }
    return;
  }

  if (allowReplay) {
    if (isReplayableDiscreteEvent(domEventName)) {
      queueDiscreteEvent(
        blockedOn,
        domEventName,
        eventSystemFlags,
        targetContainer,
        nativeEvent,
      );
      return;
    }
    if (
      queueIfContinuousEvent(
        blockedOn,
        domEventName,
        eventSystemFlags,
        targetContainer,
        nativeEvent,
      )
    ) {
      return;
    }
    clearIfContinuousEvent(domEventName, nativeEvent);
  }
  dispatchEventForPluginEventSystem(
    domEventName,
    eventSystemFlags,
    nativeEvent,
    null,
    targetContainer,
  );
}
首先进入attemptToDispatchEvent,主要做的是拿到原生DOM节点,和该节点对应的fiber,调用dispatchEventForPluginEventSystem通过插件系统,触发事件
export function attemptToDispatchEvent(
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
  targetContainer: EventTarget,
  nativeEvent: AnyNativeEvent,
): null | Container | SuspenseInstance {
  // TODO: Warn if _enabled is false.
  // 获取原生dom节点,一般是nativeEvent.target
  const nativeEventTarget = getEventTarget(nativeEvent);
  // 获取原生dom节点对应的fiber
  let targetInst = getClosestInstanceFromNode(nativeEventTarget);

  if (targetInst !== null) {
    const nearestMounted = getNearestMountedFiber(targetInst);
    // tree已经被卸载
    if (nearestMounted === null) {
      targetInst = null;
    } else {
      const tag = nearestMounted.tag;
      if (tag === SuspenseComponent) {
        const instance = getSuspenseInstanceFromFiber(nearestMounted);
        if (instance !== null) {
          return instance;
        }
        targetInst = null;
      } else if (tag === HostRoot) {
        const root: FiberRoot = nearestMounted.stateNode;
        if (root.hydrate) {

          return getContainerFromFiber(nearestMounted);
        }
        targetInst = null;
      } else if (nearestMounted !== targetInst) {

        targetInst = null;
      }
    }
  }
  dispatchEventForPluginEventSystem(
    domEventName,
    eventSystemFlags,
    nativeEvent,
    targetInst,
    targetContainer,
  );
  // We're not blocked on anything.
  return null;
}
dispatchEventForPluginEventSystem内部,通过一个向上的循环,找到当前rootContainerfiber,进入dispatchEventsForPlugins
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 && // IS_EVENT_HANDLE_NON_MANAGED_NODE = 1
    (eventSystemFlags & IS_NON_DELEGATED) === 0 // IS_NON_DELEGATED = 2
  ) {
    const targetContainerNode = ((targetContainer: any): Node);
    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;
          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) {
              node = ancestorInst = parentNode;
              continue mainLoop;
            }
            container = container.parentNode;
          }
        }
        node = node.return;
      }
    }
  }

  batchedEventUpdates(() =>
    dispatchEventsForPlugins(
      domEventName,
      eventSystemFlags,
      nativeEvent,
      ancestorInst,
      targetContainer,
    ),
  );
}
dispatchEventsForPlugins内部,会调用extractEvents,对事件对象进行合成,收集事件到执行路径上,并调用processDispatchQueue执行收集到组件中真正的事件
function dispatchEventsForPlugins(
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
  nativeEvent: AnyNativeEvent,
  targetInst: null | Fiber,
  targetContainer: EventTarget,
): void {
  // 一般是nativeEvent.target
  const nativeEventTarget = getEventTarget(nativeEvent);
  const dispatchQueue: DispatchQueue = [];
  // 事件对象的合成,收集事件到执行路径上
  extractEvents(
    dispatchQueue,
    domEventName,
    targetInst,
    nativeEvent,
    nativeEventTarget,
    eventSystemFlags,
    targetContainer,
  );
  // 执行收集到的组件中真正的事件
  processDispatchQueue(dispatchQueue, eventSystemFlags);
}
由于其余EventPlugin都基于SimpleEventPlugin封装,此处只对SimpleEventPlugin展开
function extractEvents(
  dispatchQueue: DispatchQueue,
  domEventName: DOMEventName,
  targetInst: null | Fiber,
  nativeEvent: AnyNativeEvent,
  nativeEventTarget: null | EventTarget,
  eventSystemFlags: EventSystemFlags,
  targetContainer: EventTarget,
): void {
  // 获取domEventName对应的ReactEventName,如click => onClick
  const reactName = topLevelEventsToReactNames.get(domEventName);
  if (reactName === undefined) {
    return;
  }
  let SyntheticEventCtor = SyntheticEvent;
  let reactEventType: string = domEventName;

  // ...省略 根据domEventName,将SyntheticEventCtor赋值为不同的事件类型

  // IS_CAPTURE_PHASE = 1 << 2 = 0b0100
  const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
  // scroll事件不冒泡
  const accumulateTargetOnly =
    !inCapturePhase &&
    domEventName === 'scroll';
  // 事件对象分发 & 收集事件
  const listeners = accumulateSinglePhaseListeners(
    targetInst,
    reactName,
    nativeEvent.type,
    inCapturePhase,
    accumulateTargetOnly,
  );
  if (listeners.length > 0) {
    // 构造合成事件
    const event = new SyntheticEventCtor(
      reactName,
      reactEventType,
      null,
      nativeEvent,
      nativeEventTarget,
    );
    dispatchQueue.push({event, listeners});
  }
}
accumulateSinglePhaseListeners中,会收集fiber中所有listener的回调。完成后,会构造React合成事件,并将其推入dispatchQueue派发队列中。
export function accumulateSinglePhaseListeners(
  targetFiber: Fiber | null,
  reactName: string | null,
  nativeEventType: string,
  inCapturePhase: boolean,
  accumulateTargetOnly: boolean,
): Array<DispatchListener> {
  // 根据事件名来识别是冒泡阶段的事件还是捕获阶段的事件
  const captureName = reactName !== null ? reactName + 'Capture' : null;
  const reactEventName = inCapturePhase ? captureName : reactName;
  // 声明存放事件监听的数组
  const listeners: Array<DispatchListener> = [];
  // 找到目标元素
  let instance = targetFiber;
  let lastHostComponent = null;
  // 从目标元素开始一直到root,累加所有的fiber对象和事件监听。
  while (instance !== null) {
    const {stateNode, tag} = instance;
    if (tag === HostComponent && stateNode !== null) {
      lastHostComponent = stateNode;
      // Standard React on* listeners, i.e. onClick or onClickCapture
      if (reactEventName !== null) {
        // instance.stateNode上有pendingProps,如onClick
        const listener = getListener(instance, reactEventName);
        if (listener != null) {
          listeners.push(
            createDispatchListener(instance, listener, lastHostComponent),
          );
        }
      }
    }
    // 如果只收集目标节点的话,那么就不用再往上收集了,直接跳出
    if (accumulateTargetOnly) {
      break;
    }
    instance = instance.return;
  }
  return listeners;
}
对事件对象进行合成,收集事件到执行路径上完成后,走到processDiaptchQueue,执行收集到组件中真正的事件
export function processDispatchQueue(
  dispatchQueue: DispatchQueue,
  eventSystemFlags: EventSystemFlags,
): void {
  // 是否是捕获阶段
  const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
  for (let i = 0; i < dispatchQueue.length; i++) {
    // 从dispatchQueue中取出事件对象和事件监听数组
    const {event, listeners} = dispatchQueue[i];
    // 将事件监听交由processDispatchQueueItemsInOrder去触发,同时传入事件对象供事件监听使用
    processDispatchQueueItemsInOrder(event, listeners, inCapturePhase);
  }
  rethrowCaughtError();
}
processDispatchQueueItemsInOrder内部,会根据是冒泡或捕获,正序或倒序遍历listeners(不难理解,收集diapstchListeners时,是从当前fiber遍历至root,所以正序遍历是冒泡的顺序),依次调用executeDispatch派发事件
function processDispatchQueueItemsInOrder(
  event: ReactSyntheticEvent,
  dispatchListeners: Array<DispatchListener>,
  inCapturePhase: boolean,
): void {
  let previousInstance;
  if (inCapturePhase) {
    // 事件捕获倒序循环
    for (let i = dispatchListeners.length - 1; i >= 0; i--) {
      const {instance, currentTarget, listener} = dispatchListeners[i];
      // onClick = e => e.stopPropagation()后
      // event的isPropagationStopped = functionThatReturnsTrue = () => true
      // 那么就不会在调用后面的listener了,下面的冒泡同理
      if (instance !== previousInstance && event.isPropagationStopped()) {
        return;
      }
      executeDispatch(event, listener, currentTarget);
      previousInstance = instance;
    }
  } else {
    // 事件冒泡正序循环
    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;
    }
  }
}
executeDispatch内部很简单,将currentTarget执行真正的事件函数后,再讲event.currentTarget重置为null
 

© kaba 2019 - 2022