事件绑定

事件绑定主要是在DOM初始化和更新阶段.

  1. 在初始化的时候会调用setInitialProperties方法
  2. 接着执行setInitialDOMProperties
  3. 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);
}

results matching ""

    No results matching ""