事件绑定

事件绑定主要是在初始化 DOM 的事件,当然在 DOM 更新过程中也会出现,不过较少,所以我们就从初始化 DOM 的时候入手来讲。在初始化的时候我们会调用一个方法叫做setInitialProperties,这里一开始就对一些类型的节点执行了一些事件绑定:

switch (tag) {
  case 'iframe':
  case 'object':
    trapBubbledEvent(TOP_LOAD, domElement);
    props = rawProps;
    break;
  case 'video':
  case 'audio':
    // Create listener for each media event
    for (let i = 0; i < mediaEventTypes.length; i++) {
      trapBubbledEvent(mediaEventTypes[i], domElement);
    }
    props = rawProps;
    break;
// ... others

这里调用的方法是trapBubbledEvent

而后面调用了方法setInitialDOMProperties来真正得初始化 DOM 属性

} else if (registrationNameModules.hasOwnProperty(propKey)) {
  if (nextProp != null) {
    if (__DEV__ && typeof nextProp !== 'function') {
      warnForInvalidEventListener(propKey, nextProp);
    }
    ensureListeningTo(rootContainerElement, propKey);
  }
}

这里判断了某个propKey是否在registrationNameModules中,而registrationNameModules是我们在初始化事件系统中注册的事件名对应的模块的对象,这就联系起来了。

这里调用了ensureListeningTo,我们来看一下代码:

function ensureListeningTo(rootContainerElement, registrationName) {
  const isDocumentOrFragment =
    rootContainerElement.nodeType === DOCUMENT_NODE ||
    rootContainerElement.nodeType === DOCUMENT_FRAGMENT_NODE
  const doc = isDocumentOrFragment
    ? rootContainerElement
    : rootContainerElement.ownerDocument
  listenTo(registrationName, doc)
}

rootContainerElement是 React 应用的挂载点,或者是HostPortalcontainer,所以这些事件其实都是通过事件代理来实现的。我们继续看listenTo的代码,他来自ReactBrowserEventEmitter.js

export function listenTo(
  registrationName: string,
  mountAt: Document | Element,
) {
  const isListening = getListeningForDocument(mountAt)
  const dependencies = registrationNameDependencies[registrationName]

  for (let i = 0; i < dependencies.length; i++) {
    const dependency = dependencies[i]
    if (!(isListening.hasOwnProperty(dependency) && isListening[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.
          isListening[TOP_BLUR] = true
          isListening[TOP_FOCUS] = true
          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:
          // By default, listen on the top level to all non-media events.
          // Media events don't bubble so adding the listener wouldn't do anything.
          const isMediaEvent = mediaEventTypes.indexOf(dependency) !== -1
          if (!isMediaEvent) {
            trapBubbledEvent(dependency, mountAt)
          }
          break
      }
      isListening[dependency] = true
    }
  }
}

可以看到除了一些特定的事件调用trapCapturedEvent之外,其他都绑定trapBubbledEvent,需要注意的是,绑定的时候我们需要获取某个事件的dependencies,来自registrationNameDependencies。其实看这两个方法的名字就可以知道,他们分别监听的是捕获和冒泡阶段。接下去我们就来看看这两个方法做了什么。

export function trapBubbledEvent(
  topLevelType: DOMTopLevelEventType,
  element: Document | Element,
) {
  if (!element) {
    return null
  }
  const dispatch = isInteractiveTopLevelEventType(topLevelType)
    ? dispatchInteractiveEvent
    : dispatchEvent

  addEventBubbleListener(
    element,
    getRawEventName(topLevelType),
    // Check if interactive and wrap in interactiveUpdates
    dispatch.bind(null, topLevelType),
  )
}

export function trapCapturedEvent(
  topLevelType: DOMTopLevelEventType,
  element: Document | Element,
) {
  if (!element) {
    return null
  }
  const dispatch = isInteractiveTopLevelEventType(topLevelType)
    ? dispatchInteractiveEvent
    : dispatchEvent

  addEventCaptureListener(
    element,
    getRawEventName(topLevelType),
    // Check if interactive and wrap in interactiveUpdates
    dispatch.bind(null, topLevelType),
  )
}

dispatchInteractiveEventdispatchEvent分别对应不同优先级的事件,前者优先级较高,如果处于ConcurrentMode产生的expirationTime会较小。这两个方法我们到讲解事件触发过程中去讲解。

addEventBubbleListeneraddEventCaptureListener这两个方法就很简单了。

export function addEventBubbleListener(
  element: Document | Element,
  eventType: string,
  listener: Function,
): void {
  element.addEventListener(eventType, listener, false)
}

export function addEventCaptureListener(
  element: Document | Element,
  eventType: string,
  listener: Function,
): void {
  element.addEventListener(eventType, listener, true)
}

到这里我们就把事件通过事件代理的方式绑定到了container对象上。当然对于特殊的节点的不会冒泡的事件,在setInitialProperties中已经事先直接绑定到节点上了。

results matching ""

    No results matching ""

    Jokcy的二维码

    扫码添加Jokcy,更多更新更优质的前端学习内容不断更新中,期待与你一起成长!

    Jokcy的二维码