React事件系统

73 阅读6分钟

目录

前言

本文是React源码学习系列第八篇,该系列整体都基于React18.0.0版本源码。旨在学习过程中记录一些个人的理解。该篇介绍React 事件系统。

概念

更新流程通常由“产生交互”开始,“交互”则与各种事件相关,“事件”由React事件系统产生。事件系统存在的意义在于--React用Fiber Tree数据结构描述UI,事件系统则基于Fiber Tree描述UI交互。

SyntheticEvent(合成事件)

是对浏览器原生事件对象的一层封装,拥有与原生事件相同的API,抹平了不同浏览器在“事件对象”间的差异,但是对于不支持某一事件的浏览器,不提供polyfill。

模拟实现事件传播机制。

利用事件委托的原理,React基于Fiber Tree实现了事件的“捕获、目标、冒泡”流程,并在这套事件传播机制中加入了许多“行特性”,比如:

  1. 不同事件对应不同优先级。
  2. 定制事件名。
  3. 定制事件行为。

React事件与原生事件对应关系

let registrationNameDependencies = {
    "onClick": ["click"],
    "onClickCapture": ["click"],
    "onInput": ["input"],
    "onInputCapture": ["input"],
    // onChange事件由以下原生事件合成
    "onChange": ["change", "click", "focusin", "focusout", "input", "keydown", "keyup", "selectionchange"],
    "onChangeCapture": ["change", "click", "focusin", "focusout", "input", "keydown", "keyup", "selectionchange"],
    "onSelect": ["focusout", "contextmenu", "dragend", "focusin", "keydown", "keyup", "mousedown", "mouseup", "selectionchange"],
 // 忽略其他...
}

合成事件对象工厂函数

根据传入不同的原生事件对象特有属性,返回SyntheticBaseEvent构造函数。

function createSyntheticEvent(Interface: EventInterfaceType) {
  function SyntheticBaseEvent(
    reactName: string | null,
    reactEventType: string,
    targetInst: Fiber,
    nativeEvent: {[propName: string]: mixed},
    nativeEventTarget: null | EventTarget,
  ) {
    this._reactName = reactName;
    this._targetInst = targetInst;
    this.type = reactEventType;
    this.nativeEvent = nativeEvent;
    this.target = nativeEventTarget;
    this.currentTarget = null;
    // 把传进来的属性,合并到实例上。
    for (const propName in Interface) {
      if (!Interface.hasOwnProperty(propName)) {
        continue;
      }
      const normalize = Interface[propName];
      // 除了函数,其他默认都是给0,所以会执行函数
      if (normalize) { 
        this[propName] = normalize(nativeEvent);
      } else {
        this[propName] = nativeEvent[propName];
      }
    }

    const defaultPrevented =
      nativeEvent.defaultPrevented != null
        ? nativeEvent.defaultPrevented
        : nativeEvent.returnValue === false;
    if (defaultPrevented) {
      this.isDefaultPrevented = functionThatReturnsTrue;
    } else {
      this.isDefaultPrevented = functionThatReturnsFalse;
    }
    this.isPropagationStopped = functionThatReturnsFalse;
    return this;
  }
  // 扩展原型方法
  Object.assign(SyntheticBaseEvent.prototype, {
    // 模拟阻止默认事件方法。
    preventDefault: function() {
      this.defaultPrevented = true;
      const event = this.nativeEvent;
      if (!event) {
        return;
      }
      // 执行原生阻止默认事件方法。
      if (event.preventDefault) {
        event.preventDefault();
      } else if (typeof event.returnValue !== 'unknown') {
        event.returnValue = false;
      }
      // 设为true
      this.isDefaultPrevented = functionThatReturnsTrue;
    },
    // 模拟阻止冒泡方法。
    stopPropagation: function() {
      const event = this.nativeEvent;
      if (!event) {
        return;
      }
      // 执行原生阻止冒泡方法。
      if (event.stopPropagation) {
        event.stopPropagation();
      } else if (typeof event.cancelBubble !== 'unknown') {
        event.cancelBubble = true;
      }
      // 设为true
      this.isPropagationStopped = functionThatReturnsTrue;
    },
  });
  return SyntheticBaseEvent;
}

代码梳理

function createRoot(
  container: Element | DocumentFragment,
  options?: CreateRootOptions,
): RootType {
  // container为document.getElementById('root');
  const rootContainerElement: Document | Element | DocumentFragment =
    container.nodeType === COMMENT_NODE
      ? (container.parentNode: any)
      : container; //判断传入的container是不是注释标签,是就取它父元素
  // 事件绑定
  listenToAllSupportedEvents(rootContainerElement);
}

listenToAllSupportedEvents

判断是否是捕获阶段事件,没有冒泡阶段的。

function listenToAllSupportedEvents(rootContainerElement: EventTarget) { 
  if (!(rootContainerElement: any)[listeningMarker]) {
    (rootContainerElement: any)[listeningMarker] = true;
    allNativeEvents.forEach(domEventName => {
      // 捕获事件
      if (!nonDelegatedEvents.has(domEventName)) {
          listenToNativeEvent(domEventName, false, rootContainerElement);
      }
      // 冒泡事件
      listenToNativeEvent(domEventName, true, rootContainerElement); 
    });
  }
}

listenToNativeEvent

没有冒泡阶段的方法,打上捕获标记。

function listenToNativeEvent(
  domEventName: DOMEventName,
  isCapturePhaseListener: boolean,
  target: EventTarget,
): void {
  let eventSystemFlags = 0;
  // 是否捕获阶段
  if (isCapturePhaseListener) {
    eventSystemFlags |= IS_CAPTURE_PHASE;
  }
  addTrappedEventListener(
    target,
    domEventName,
    eventSystemFlags,
    isCapturePhaseListener,
  );
}

addTrappedEventListener

  1. 给事件包装优先级
  2. 根据是否捕获阶段,注册对应阶段的事件
function addTrappedEventListener(
  targetContainer: EventTarget,
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
  isCapturePhaseListener: boolean,
  isDeferredListenerForLegacyFBSupport?: boolean,
) {
  // 事件包装优先级
  let listener = createEventListenerWrapperWithPriority(
    targetContainer,
    domEventName,
    eventSystemFlags,
  );
  let unsubscribeListener;

  if (isCapturePhaseListener) {
      //注册捕获事件
      unsubscribeListener = addEventCaptureListener(
        targetContainer,
        domEventName,
        listener,
      );
  } else {
      //注册冒泡事件
      unsubscribeListener = addEventBubbleListener(
        targetContainer,
        domEventName,
        listener,
      );
  }
}

包装事件优先级

各种优先级都是手动设置对应优先级,然后调用dispatchEvent方法。

function createEventListenerWrapperWithPriority(
  targetContainer: EventTarget,
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
): Function {
  // 根据事件类型获取事件优先级
  const eventPriority = getEventPriority(domEventName);
  let listenerWrapper;
  // 根据事件优先级注册不同的dispatch函数
  switch (eventPriority) {
    case DiscreteEventPriority: //最高优先级sync,第一车道
      listenerWrapper = dispatchDiscreteEvent;
      break;
    case ContinuousEventPriority: //第三车道优先级
      listenerWrapper = dispatchContinuousEvent;
      break;
    case DefaultEventPriority:// 最低优先级第五车道
    default:
      listenerWrapper = dispatchEvent;
      break;
  }
  return listenerWrapper.bind(
    null,
    domEventName,
    eventSystemFlags,
    targetContainer,
  );
}

dispatchEvent

找到触发事件元素的对应fiberNode。

// 保留核心代码
function dispatchEvent(
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
  targetContainer: EventTarget,
  nativeEvent: AnyNativeEvent, // 原生事件对象
) {
    // 找到触发事件的元素对应的fiberNode,赋值给return_targetInst
    findInstanceBlockingEvent(
      domEventName,
      eventSystemFlags,
      targetContainer,
      nativeEvent,
    );
    // 触发的主要函数
    dispatchEventForPluginEventSystem(
      domEventName,
      eventSystemFlags,
      nativeEvent,
      return_targetInst,
      targetContainer,
    );
    return;
}

dispatchEventForPluginEventSystem

在批处理环境下执行事件。

function dispatchEventForPluginEventSystem(
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
  nativeEvent: AnyNativeEvent,
  targetInst: null | Fiber,
  targetContainer: EventTarget,
): void {
  let ancestorInst = targetInst;
  // 批处理
  batchedUpdates(() =>
    dispatchEventsForPlugins(
      domEventName,
      eventSystemFlags,
      nativeEvent,
      ancestorInst,
      targetContainer,
    ),
  );
}

dispatchEventsForPlugins

  1. 收集路径上的同类事件
  2. 模拟原生事件流程,根据调用事件执行阶段(捕获或者冒泡)执行收集到的时间。
function dispatchEventsForPlugins(
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
  nativeEvent: AnyNativeEvent,
  targetInst: null | Fiber,
  targetContainer: EventTarget,
): void {
  // event.target
  const nativeEventTarget = getEventTarget(nativeEvent);
  const dispatchQueue: DispatchQueue = [];
  // 收集路径上的同类事件,并创建合成事件对象。
  extractEvents(
    dispatchQueue,
    domEventName,
    targetInst,
    nativeEvent,
    nativeEventTarget,
    eventSystemFlags,
    targetContainer,
  );
  // 模拟原生事件执行。
  processDispatchQueue(dispatchQueue, eventSystemFlags);
}

extractEvents

根据触发的事件名称,收集Fiber Tree上的同类事件。并根据不同的事件类型,创建对应的合成事件对象。

function extractEvents(
  dispatchQueue: DispatchQueue,
  domEventName: DOMEventName,
  targetInst: null | Fiber,
  nativeEvent: AnyNativeEvent,
  nativeEventTarget: null | EventTarget,
  eventSystemFlags: EventSystemFlags,
  targetContainer: EventTarget,
): void {
  const reactName = topLevelEventsToReactNames.get(domEventName); // 拿到原生事件对应的react事件
  if (reactName === undefined) {
    return;
  }
  let SyntheticEventCtor = SyntheticEvent;
  let reactEventType: string = domEventName;
  switch (domEventName) {
    case 'keydown':
    case 'keyup':
      SyntheticEventCtor = SyntheticKeyboardEvent;
      break;
    default:
      break;
  }
  const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
    // 收集事件
    const listeners = accumulateSinglePhaseListeners(
      targetInst,
      reactName,
      nativeEvent.type,
      inCapturePhase,
      accumulateTargetOnly,
      nativeEvent,
    );
    // listeners触发节点到HostRootFiber路径上的所有该类型事件列表。
    if (listeners.length > 0) {
      // 合成事件对象
      const event = new SyntheticEventCtor(
        reactName,
        reactEventType,
        null,
        nativeEvent,
        nativeEventTarget,
      );
      dispatchQueue.push({event, listeners});
    }
}

accumulateSinglePhaseListeners

从触发事件的元素对应fiberNode开始,向上一直找到HostRootFiber结束,收集路径上的同类型事件。维护在一个Array里面。

function accumulateSinglePhaseListeners(
  targetFiber: Fiber | null,
  reactName: string | null,
  nativeEventType: string,
  inCapturePhase: boolean,
  accumulateTargetOnly: boolean,
  nativeEvent: AnyNativeEvent,
): Array<DispatchListener> {
  const captureName = reactName !== null ? reactName + 'Capture' : null;
  const reactEventName = inCapturePhase ? captureName : reactName;
  let listeners: Array<DispatchListener> = [];

  let instance = targetFiber;
  let lastHostComponent = null;

  // 从事件触发点到HostRootFiber收集事件
  while (instance !== null) {
    const {stateNode, tag} = instance;
    // 处理HostComponents上的监听器
    if (tag === HostComponent && stateNode !== null) {
      lastHostComponent = stateNode;
      if (reactEventName !== null) {
        const listener = getListener(instance, reactEventName);
        if (listener != null) {
          // 返回 {instance,listener,lastHostComponent}
          listeners.push(
            createDispatchListener(instance, listener, lastHostComponent),
          );
        }
      }
    }
    instance = instance.return;
  }
  return listeners;
}

getListener

从stateNode上拿到对应事件的回调函数。

function getListener(
  inst: Fiber,
  registrationName: string,
): Function | null {
  const stateNode = inst.stateNode;
  const props = getFiberCurrentPropsFromNode(stateNode);
  const listener = props[registrationName];
  return listener;
}

event2.jpg

processDispatchQueue

为收集到的事件列表模拟原生事件流程,捕获、目标、冒泡。

function processDispatchQueue(
  dispatchQueue: DispatchQueue,
  eventSystemFlags: EventSystemFlags,
): void {
  const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
  for (let i = 0; i < dispatchQueue.length; i++) {
    const {event, listeners} = dispatchQueue[i];
    processDispatchQueueItemsInOrder(event, listeners, inCapturePhase);
  }
}

processDispatchQueueItemsInOrder

模拟实现捕获阶段事件及冒泡阶段事件。

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

事件执行

function executeDispatch(
  event: ReactSyntheticEvent, // 合成事件对象
  listener: Function,
  currentTarget: EventTarget,
): void {
  const type = event.type || 'unknown-event';
  event.currentTarget = currentTarget;
  invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event);
  event.currentTarget = null;
}

最终调用事件。

最终生产环境是调用invokeGuardedCallbackProd方法,执行实际的事件。

function invokeGuardedCallbackProd<A, B, C, D, E, F, Context>(
  name: string | null,
  func: (a: A, b: B, c: C, d: D, e: E, f: F) => mixed,
  context: Context,
  a: A,
  b: B,
  c: C,
  d: D,
  e: E,
  f: F,
) {
  // 拿到调用事件处理函数的实际传参
  const funcArgs = Array.prototype.slice.call(arguments, 3); 
  try {
    // 执行的时候绑定context 为undefined
    func.apply(context, funcArgs); 
  } catch (error) {
    this.onError(error);
  }
}

批处理

个人理解18的架构下,这个应该没有用了,按照lane模型来批量处理的,每一次调度都会选出一个最高优先级,同样优先级的更新任务会在同一个更新流程里面执行。

function batchedUpdates<A, R>(fn: A => R, a: A): R {
  // 缓存之前执行上下文
  const prevExecutionContext = executionContext;
  // 执行上下文加上批处理上下文
  executionContext |= BatchedContext;
  try {
    return fn(a);
  } finally {
    // 恢复之前执行上下文
    executionContext = prevExecutionContext;
  }
}

参考

React设计原理 - 卡颂