事件绑定
事件绑定主要是在DOM初始化和更新阶段.
- 在初始化的时候会调用setInitialProperties方法
- 接着执行setInitialDOMProperties
- setInitialDOMProperties对props的key进行判断,不同的props有不同的处理方式,如style、class、autoFocus,innerHTML等,如果registrationNameModules.hasOwnProperty(propKey)为真,就表明是个注册过得事件,调用ensureListeningTo开始绑定
ensureListeningTo
if (registrationNameModules.hasOwnProperty(propKey)) {
      if (nextProp != null) {
        if (true && typeof nextProp !== 'function') {
          warnForInvalidEventListener(propKey, nextProp);
        }
        ensureListeningTo(rootContainerElement, propKey);
}
function ensureListeningTo(rootContainerElement, registrationName) {
  var isDocumentOrFragment = rootContainerElement.nodeType === DOCUMENT_NODE || rootContainerElement.nodeType === DOCUMENT_FRAGMENT_NODE;
  var doc = isDocumentOrFragment ? rootContainerElement : rootContainerElement.ownerDocument;
  listenTo(registrationName, doc);
}
rootContainerElement是 React 应用的挂载点,这个方法首先判断rootContainerElement是不是一个 document或者 Fragment,二者都不是的话,就设置doc为rootContainerElement.ownerDocument, 也就是当前节点的顶层的 document 对象。
可以看到事件委托就是在这里实现的,所有的事件最终都会被委托到 document 或者 fragment上去,大部分情况下都是 document。
listenTo
export function listenTo(
  registrationName: string,
  mountAt: Document | Element | Node,
): void {
    //获取监听的节点
  const listeningSet = getListeningSetForElement(mountAt);
  //获取事件的依赖
  const dependencies = registrationNameDependencies[registrationName];
  for (let i = 0; i < dependencies.length; i++) {
    const dependency = dependencies[i];
    if (!listeningSet.has(dependency)) {
      switch (dependency) {
        case TOP_SCROLL:
          trapCapturedEvent(TOP_SCROLL, mountAt);
          break;
        case TOP_FOCUS:
        case TOP_BLUR:
          trapCapturedEvent(TOP_FOCUS, mountAt);
          trapCapturedEvent(TOP_BLUR, mountAt);
          // We set the flag for a single dependency later in this function,
          // but this ensures we mark both as attached rather than just one.
          listeningSet.add(TOP_BLUR);
          listeningSet.add(TOP_FOCUS);
          break;
        case TOP_CANCEL:
        case TOP_CLOSE:
          if (isEventSupported(getRawEventName(dependency))) {
            trapCapturedEvent(dependency, mountAt);
          }
          break;
        case TOP_INVALID:
        case TOP_SUBMIT:
        case TOP_RESET:
          // We listen to them on the target DOM elements.
          // Some of them bubble so we don't want them to fire twice.
          break;
        default:
          const isMediaEvent = mediaEventTypes.indexOf(dependency) !== -1;
          if (!isMediaEvent) {
            trapBubbledEvent(dependency, mountAt);
          }
          break;
      }
      listeningSet.add(dependency);
    }
  }
}
可以看到除了一些特定的事件调用trapCapturedEvent外,多数事件调用trapBubbledEvent,分别对应捕获和冒泡阶段。
trapCapturedEvent和trapBubbledEvent
都调用了trapEventForPluginEventSystem只是最后一个参数不同,一个为true,一个为false 表示是否捕获。
由于事件委托到了document上,所有的合成事件回调函数都是在冒泡阶段触发的,所以无论是React的冒泡还是捕获事件,都是晚于原生冒泡和捕获事件的响应的,如果原生事件调用了e.stopPropagation(),那么React事件将无法响应
export function trapBubbledEvent(
  topLevelType: DOMTopLevelEventType,
  element: Document | Element | Node,
): void {
  trapEventForPluginEventSystem(element, topLevelType, false);
}
export function trapCapturedEvent(
  topLevelType: DOMTopLevelEventType,
  element: Document | Element | Node,
): void {
  trapEventForPluginEventSystem(element, topLevelType, true);
}
trapEventForPluginEventSystem
首先判断事件的优先级,不同优先级的有不同的处理方法。
export const DiscreteEvent: EventPriority = 0;
export const UserBlockingEvent: EventPriority = 1;
export const ContinuousEvent: EventPriority = 2;
可以看到listener并不是我们自己写的事件回调,这里的listener只是记住了是什么类型的事件。
addEventBubbleListener和addEventCaptureListener
function trapEventForPluginEventSystem(
  element: Document | Element | Node,
  topLevelType: DOMTopLevelEventType,
  capture: boolean,
): void {
  let listener;
  switch (getEventPriority(topLevelType)) {
    case DiscreteEvent:
      listener = dispatchDiscreteEvent.bind(
        null,
        topLevelType,
        PLUGIN_EVENT_SYSTEM,
      );
      break;
    case UserBlockingEvent:
      listener = dispatchUserBlockingUpdate.bind(
        null,
        topLevelType,
        PLUGIN_EVENT_SYSTEM,
      );
      break;
    case ContinuousEvent:
    default:
      listener = dispatchEvent.bind(null, topLevelType, PLUGIN_EVENT_SYSTEM);
      break;
  }
  const rawEventName = getRawEventName(topLevelType);
  if (capture) {
    addEventCaptureListener(element, rawEventName, listener);
  } else {
    addEventBubbleListener(element, rawEventName, listener);
  }
}
function addEventBubbleListener(element, eventType, listener) {
  element.addEventListener(eventType, listener, false);
}
function addEventCaptureListener(element, eventType, listener) {
  element.addEventListener(eventType, listener, true);
}
到这里就通过事件代理的方式在container对象上绑定到了事件。
以click事件为例,会调用dispatchDiscreteEvent ,最终还是dispatchEvent方法被调用。该方法将在事件分发小节讲述。
function dispatchDiscreteEvent(topLevelType, eventSystemFlags, nativeEvent) {
  flushDiscreteUpdatesIfNeeded(nativeEvent.timeStamp);
  discreteUpdates(dispatchEvent, topLevelType, eventSystemFlags, nativeEvent);
}