React事件系统

185 阅读4分钟

一、事件注册

入口

  registerSimpleEvents();
  registerEvents$2();
  registerEvents$1();
  registerEvents$3();
  registerEvents();

registerSimpleEvent

  • 将dom事件名和react事件名映射到topLevelEventsToReactNames集合
  • 调用registerTwoPhaseEvent函数
  var topLevelEventsToReactNames = new Map(); 

    // domEventName dom事件名
    // reactName React事件名
  function registerSimpleEvent(domEventName, reactName) {
    topLevelEventsToReactNames.set(domEventName, reactName);
    registerTwoPhaseEvent(reactName, [domEventName]);
  }

registerTwoPhaseEvent

注册冒泡事件和捕获事件

  • React官方文档对Capture有如下解释 如需注册捕获阶段的事件处理函数,则应为事件名添加 Capture。例如,处理捕获阶段的点击事件请使用 onClickCapture,而不是 onClick
  function registerTwoPhaseEvent(registrationName, dependencies) {
    registerDirectEvent(registrationName, dependencies);
    // 在React事件名称后加上Capture表示捕获事件
    registerDirectEvent(registrationName + "Capture", dependencies);
  }

registerDirectEvent

    // 原生事件名的集合
     var allNativeEvents = new Set();
    // registrationNameDependencies 负责存储{React事件名:[原生事件名]}的映射
     var registrationNameDependencies = {};
    // possibleRegistrationNames 负责存储{原生事件名:React事件名}的映射
  var possibleRegistrationNames = {}
    // registrationName React事件名
    // 原生DOM事件名 dependencies
  function registerDirectEvent(registrationName, dependencies) {
    {
        // 如果registrationNameDependencies事件已经存在了 说明事件系统可能被重复注册
      if (registrationNameDependencies[registrationName]) {
        error(
          "EventRegistry: More than one plugin attempted to publish the same " +
            "registration name, `%s`.",
          registrationName
        );
      }
    }
    // 将事件的依赖关系存入到 registrationNameDependencies
    registrationNameDependencies[registrationName] = dependencies;

    {
      var lowerCasedName = registrationName.toLowerCase();
      possibleRegistrationNames[lowerCasedName] = registrationName;

      if (registrationName === "onDoubleClick") {
        possibleRegistrationNames.ondblclick = registrationName;
      }
    }
    // 遍历dependencies 并将事件名放入allNativeEvents中
    for (var i = 0; i < dependencies.length; i++) {
      allNativeEvents.add(dependencies[i]);
    }
  }
  • 我们看来下注册事件后全局状态的数据模型
  • allNativeEvents image.png
  • registrationNameDependencies image.png
  • possibleRegistrationNames image.png

事件绑定

listenToAllSupportedEvents

  • 绑定所有的事件
  • 执行listenToNativeEvent
  // rootContainerElement 根元素
  function listenToAllSupportedEvents(rootContainerElement) {
  // listeningMarker 是否加载事件的标记
  // 可以在原生dom上看到该方法 如下图
  // 当注册之后 标记为true 下次再执行该方法时 直接跳过
    if (!rootContainerElement[listeningMarker]) {
      rootContainerElement[listeningMarker] = true;
      // 遍历allNativeEvents
      allNativeEvents.forEach(function (domEventName) {
        // selectionchange事件因为不冒泡 所以需要单独处理
        if (domEventName !== "selectionchange") {
        // nonDelegatedEvents 非捕获的事件集合
        // 如果该事件是非捕获的,直接冒泡
          if (!nonDelegatedEvents.has(domEventName)) {
            listenToNativeEvent(domEventName, false, rootContainerElement);
          }

          listenToNativeEvent(domEventName, true, rootContainerElement);
        }
      });
      
      // 处理 selectionchange 事件
      var ownerDocument =
        rootContainerElement.nodeType === DOCUMENT_NODE
          ? rootContainerElement
          : rootContainerElement.ownerDocument;

      if (ownerDocument !== null) {
        // The selectionchange event also needs deduplication
        // but it is attached to the document.
        if (!ownerDocument[listeningMarker]) {
          ownerDocument[listeningMarker] = true;
          listenToNativeEvent("selectionchange", false, ownerDocument);
        }
      }
    }
  }

root上的listening标记

image.png

listenToNativeEvent

  • 计算eventSystemFlags
  • 执行addTrappedEventListener
// domEventName dom事件名
// isCapturePhaseListener 是否是冒泡
// target 根dom元素
  function listenToNativeEvent(domEventName, isCapturePhaseListener, target) {
    {
    // 对特殊情况进行抛错
      if (nonDelegatedEvents.has(domEventName) && !isCapturePhaseListener) {
        error(
          'Did not expect a listenToNativeEvent() call for "%s" in the bubble phase. ' +
            "This is a bug in React. Please file an issue.",
          domEventName
        );
      }
    }

    var eventSystemFlags = 0;

    if (isCapturePhaseListener) {
    // IS_CAPTURE_PHASE 表示2进制的 0b100
    // 当我们判断是否是冒泡时候可以直接使用二进制进行计算
      eventSystemFlags |= IS_CAPTURE_PHASE;
    }

    addTrappedEventListener(
      target,
      domEventName,
      eventSystemFlags,
      isCapturePhaseListener
    );
  }

addTrappedEventListener

  • 执行createEventListenerWrapperWithPriority创建具有优先级的监听器
  • 根据passave和是否冒泡执行不同的添加事件函数addEventCaptureListenerWithPassiveFlag、addEventCaptureListener、addEventBubbleListenerWithPassiveFlag、addEventBubbleListener
// targetContainer 根DOM元素
// domEventName 事件名
// eventSystemFlags 事件系统标识
// isCapturePhaseListener // 是否是捕获的listener
  function addTrappedEventListener(
    targetContainer,
    domEventName,
    eventSystemFlags,
    isCapturePhaseListener
  ) {
  // 创建具有优先级的listenner
    var listener = createEventListenerWrapperWithPriority(
      targetContainer,
      domEventName,
      eventSystemFlags
    ); // If passive option is not supported, then the event will be
    // active and not passive.

    var isPassiveListener = undefined;
    
    // 处理passivepassive带来的bug // 可以忽略
    if (passiveBrowserEventsSupported) {
      // Browsers introduced an intervention, making these events
      // passive by default on document. React doesn't bind them
      // to document anymore, but changing this now would undo
      // the performance wins from the change. So we emulate
      // the existing behavior manually on the roots now.
      // https://github.com/facebook/react/issues/19651
      if (
        domEventName === "touchstart" ||
        domEventName === "touchmove" ||
        domEventName === "wheel"
      ) {
        isPassiveListener = true;
      }
    }

    targetContainer = targetContainer;
    var unsubscribeListener; // When legacyFBSupport is enabled, it's for when we

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

createEventListenerWrapperWithPriority

  • 通过getEventPriority获取事件的优先级

getEventPriority

  • React18中将事件的优先级分为DiscreteEventPriority、ContinuousEventPriority、DefaultEventPriority、IdleEventPriority、ContinuousEventPriority 根据事件获取优先级
function getEventPriority(domEventName) {
    switch (domEventName) {
      // Used by SimpleEventPlugin:
      case "cancel":
      case "click":
      case "close":
      case "contextmenu":
      case "copy":
      case "cut":
      case "auxclick":
      case "dblclick":
      case "dragend":
      case "dragstart":
      case "drop":
      case "focusin":
      case "focusout":
      case "input":
      case "invalid":
      case "keydown":
      case "keypress":
      case "keyup":
      case "mousedown":
      case "mouseup":
      case "paste":
      case "pause":
      case "play":
      case "pointercancel":
      case "pointerdown":
      case "pointerup":
      case "ratechange":
      case "reset":
      case "resize":
      case "seeked":
      case "submit":
      case "touchcancel":
      case "touchend":
      case "touchstart":
      case "volumechange": // Used by polyfills:
      // eslint-disable-next-line no-fallthrough

      case "change":
      case "selectionchange":
      case "textInput":
      case "compositionstart":
      case "compositionend":
      case "compositionupdate": // Only enableCreateEventHandleAPI:
      // eslint-disable-next-line no-fallthrough

      case "beforeblur":
      case "afterblur": // Not used by React but could be by user code:
      // eslint-disable-next-line no-fallthrough

      case "beforeinput":
      case "blur":
      case "fullscreenchange":
      case "focus":
      case "hashchange":
      case "popstate":
      case "select":
      case "selectstart":
        return DiscreteEventPriority;

      case "drag":
      case "dragenter":
      case "dragexit":
      case "dragleave":
      case "dragover":
      case "mousemove":
      case "mouseout":
      case "mouseover":
      case "pointermove":
      case "pointerout":
      case "pointerover":
      case "scroll":
      case "toggle":
      case "touchmove":
      case "wheel": // Not used by React but could be by user code:
      // eslint-disable-next-line no-fallthrough

      case "mouseenter":
      case "mouseleave":
      case "pointerenter":
      case "pointerleave":
        return ContinuousEventPriority;

      case "message": {
        // We might be in the Scheduler callback.
        // Eventually this mechanism will be replaced by a check
        // of the current priority on the native scheduler.
        var schedulerPriority = getCurrentPriorityLevel();

        switch (schedulerPriority) {
          case ImmediatePriority:
            return DiscreteEventPriority;

          case UserBlockingPriority:
            return ContinuousEventPriority;

          case NormalPriority:
          case LowPriority:
            // TODO: Handle LowSchedulerPriority, somehow. Maybe the same lane as hydration.
            return DefaultEventPriority;

          case IdlePriority:
            return IdleEventPriority;

          default:
            return DefaultEventPriority;
        }
      }

      default:
        return DefaultEventPriority;
    }
  }

addEventCaptureListenerWithPassiveFlag addEventCaptureListener addEventBubbleListenerWithPassiveFlag addEventBubbleListener

  • 根据不同的类型绑定监听函数
  function addEventBubbleListener(target, eventType, listener) {
   target.addEventListener(eventType, listener, false);
   return listener;
 }
 function addEventCaptureListener(target, eventType, listener) {
   target.addEventListener(eventType, listener, true);
   return listener;
 }
 function addEventCaptureListenerWithPassiveFlag(
   target,
   eventType,
   listener,
   passive
 ) {
   target.addEventListener(eventType, listener, {
     capture: true,
     passive: passive,
   });
   return listener;
 }
 function addEventBubbleListenerWithPassiveFlag(
   target,
   eventType,
   listener,
   passive
 ) {
   target.addEventListener(eventType, listener, {
     passive: passive,
   });
   return listener;
 }

事件触发