Skip to content

React 事件处理 #47

@creamidea

Description

@creamidea

事件绑定

从 createRoot 函数开始

function createRoot(container) {
  ...

  const rootContainerElement: Document | Element | DocumentFragment =
  container.nodeType === COMMENT_NODE
    ? (container.parentNode: any)
    : container;
  listenToAllSupportedEvents(rootContainerElement);

  ...
}

listenToAllSupportedEvents 进行原生事件绑定。也会处理 nonDelegatedEvents,selectionchange 事件等

function listenToAllSupportedEvents() {
  // 核心代码,listenToNativeEvent 调用 addTrappedEventListener
  // eventSystemFlags |= IS_CAPTURE_PHASE;
  listenToNativeEvent(domEventName, true, rootContainerElement);
}

addTrappedEventListener 函数根据不同事件,构造不同优先级的处理函数(createEventListenerWrapperWithPriority),得到该函数之后进行原生 DOM 事件绑定,绑定到根 DOM 节点。比如,click 事件,处理函数就是 dispatchDiscreteEvent,默认是 dispatchEvent。并通过 bind 的方式,固定参数:domEventName、eventSystemFlags、targetContainer(即 root dom 节点实例)

至此,绑定原生事件的过程结束,后续就是等待用户交互,触发原生事件,进入诸如 dispatchEvent 函数进行处理。

事件处理

进入诸如 dispatchEvent 函数进行处理。其最总调用 dispatchEventOriginal 函数处理。

function dispatchEventOriginal() {
  ...
  const blockedOn = findInstanceBlockingEvent(
    domEventName,
    eventSystemFlags,
    targetContainer,
    nativeEvent,
  );
  if (blockedOn === null) {
    dispatchEventForPluginEventSystem(
      domEventName,
      eventSystemFlags,
      nativeEvent,
      return_targetInst,
      targetContainer,
    );
    ...
    return;
  }
  ...
}

function findInstanceBlockingEvent() {
  // 该值会在函数中处理,有值的 2 种情况
  // - targetInst NearestMountedFiber 是 Suspense
  // - Hydrating 情况下,targetInst NearestMountedFiber 是 HostRoot
  return_targetInst = null;

  const nativeEventTarget = getEventTarget(nativeEvent);

  // targetInst 是根据当前事件的 DOM 节点中存储的 internalInstanceKey 找到对应 fiber 实例
  let targetInst = getClosestInstanceFromNode(nativeEventTarget);

  ...

  return null
}

blockedOn 有值的 2 种情况,有值则会调用 queueDiscreteEvent 放入到 queuedDiscreteEvents 队列(retryIfBlockedOn 会消费)

  • targetInst NearestMountedFiber 是 Suspense
  • 注入 Hydrating 情况下,targetInst NearestMountedFiber 是 HostRoot
  • 注:targetInst 是根据当前事件的 DOM 节点中存储的 internalInstanceKey 找到对应 fiber 实例
function dispatchEventForPluginEventSystem() {
  // 注意,这里的 targetInst 是👆说的 return_targetInst 这个全局变量
  // 即处理 Suspense 或者 HostRoot 情况
  if (targetInst !== null) {
    ...
  }
  
  batchedUpdates(() =>
    dispatchEventsForPlugins(
      domEventName,
      eventSystemFlags,
      nativeEvent,
      ancestorInst,
      targetContainer,
    ),
  );
}

dispatchEventsForPlugins 函数就是调用注册的 Plugin 的 extract 方法,提取事件。比如 SimpleEventPlugin.extractEvents 函数内会通过 accumulateSinglePhaseListeners 函数收集绑定的事件,并构造 SyntheticEvent,一起放入到 dispatchQueue 队列

function extractEvents() {
  ...
  const listeners = accumulateSinglePhaseListeners(
    targetInst,
    reactName,
    nativeEvent.type,
    inCapturePhase,
    accumulateTargetOnly,
    nativeEvent,
  );
  if (listeners.length > 0) {
    // Intentionally create event lazily.
    const event = new SyntheticEventCtor(
      reactName,
      reactEventType,
      null,
      nativeEvent,
      nativeEventTarget,
    );
    dispatchQueue.push({event, listeners});
  }
  ...
}

function accumulateSinglePhaseListeners() {
  // Accumulate all instances and listeners via the target -> root path.
}

dispatchQueue 队列会在 processDispatchQueue 函数内被处理

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];
      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;
    }
  }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions