React源码解析-事件系统

2,161 阅读8分钟

前言

本文章是基于16.13.1的react版本。故里面有部分代码逻辑和16.8.6以及之前的版本已经发生较大差异,看本文之前,欢迎大家先看下以下链接,了解react v17.0版本都做了那些升级和变更。

官方说明:React v17.0 Release Candidate: No New Features

与本文相关的:1. 更改事件委托;2. “去除事件池“概念(事件合成时)。

一、事件存储&事件注册

1. 流程图

2. 流程细节

事件注册或者说绑定一般在初始化DOM节点的时候,所以我们直接以这种场景入手来说。在初始化的时候我们会调用到finalizeInitialChildren,再进入setInitialProperties来初始化属性:

// react-dom/src/client/ReactDOMComponent.js
export function setInitialProperties(
  domElement: Element,
  tag: string,
  rawProps: Object,
  rootContainerElement: Element | Document,
): void {
  switch (tag) {
    // ...
    case 'input':
      // ...
      break;
    case 'option':
      // ...
      break;
    case 'select':
      // ...
      break;
    case 'textarea':
      // ...
      break;
    default:
      props = rawProps;
  }

  assertValidProps(tag, props);

  // 调用setInitialDOMProperties方法来真正得初始化 DOM 属性
  setInitialDOMProperties(
    tag,
    domElement,
    rootContainerElement,
    props,
    isCustomComponentTag,
  );

  // ...
}

紧接着进入setInitialDOMProperties方法中:

// react-dom/src/client/ReactDOMComponent.js
function setInitialDOMProperties(
  tag: string,
  domElement: Element,
  rootContainerElement: Element | Document,
  nextProps: Object,
  isCustomComponentTag: boolean,
):void {
    for (const propKey in nextProps) {
    const nextProp = nextProps[propKey];
    if (propKey === STYLE) {
      // ...
    } else if (registrationNameDependencies.hasOwnProperty(propKey)) {
       if (nextProp != null) {
         if (__DEV__ && typeof nextProp !== 'function') {
             warnForInvalidEventListener(propKey, nextProp);
         }
          ensureListeningTo(rootContainerElement, propKey, domElement);
       }
    } 
  }
}

当propKey在registrationNameDependencies列表中时,会调用ensureListeningTo方法。这里的registrationNameDependencies存储了React事件类型与浏览器原生事件类型映射的一个map对象。

                                           registrationNameDependencies合集

                                其中"onChange"的dependences(下面会用到)

接下来我们接续往下看下ensureListeningTo的代码:

// react-dom/src/client/ReactDOMComponent.js

export function ensureListeningTo(
  rootContainerInstance: Element | Node,
  reactPropEvent: string, // 例如onChange
  targetElement: Element | null,
): void {

  const rootContainerElement =
    rootContainerInstance.nodeType === COMMENT_NODE
      ? rootContainerInstance.parentNode // body
      : rootContainerInstance; // div#root,即当前插入的dom根节点

  listenToReactEvent(
    reactPropEvent,
    ((rootContainerElement: any): Element),
    targetElement,
  );
}

rootContainerElement是 React 应用的挂载点,接下来我们看下listenToReactEvent方法:

// react-dom/src/events/DOMPluginEventSystem.js

export function listenToReactEvent(
  reactEvent: string, // 例如onChange
  rootContainerElement: Element,
  targetElement: Element | null,
): void {
  // dependences这边可以理解为事件依赖,就是说注册某个事件,react会强制依赖其他事件。
  const dependencies = registrationNameDependencies[reactEvent];
  const dependenciesLength = dependencies.length;
  const isPolyfillEventPlugin = dependenciesLength !== 1;

  if (isPolyfillEventPlugin) {
    // 首次返回一个空的map对象
    const listenerMap = getEventListenerMap(rootContainerElement);

    // listenerMap不包含当前事件属性,就进入判断(has是判断属性是否存在,即使内容为null,也是返回true)
    // 也就是同一种事件只会注册一遍,onChange、onClick等等
    if (!listenerMap.has(reactEvent)) {
      // 给对象添加一个reactEvent属性,值为null
      listenerMap.set(reactEvent, null);
      for (let i = 0; i < dependenciesLength; i++) {
        // 循环遍历dependencies
        listenToNativeEvent(
          dependencies[i], // dependence
          false,
          rootContainerElement,
          targetElement,
        );
      }
    }
  } else {
    const isCapturePhaseListener =
      reactEvent.substr(-7) === 'Capture' &&
      reactEvent.substr(-14, 7) !== 'Pointer';
    listenToNativeEvent(
      dependencies[0],
      isCapturePhaseListener,
      rootContainerElement,
      targetElement,
    );
  }
}

const internalEventHandlersKey = '__reactEvents/div> + randomKey;
export function getEventListenerMap(node: EventTarget): ElementListenerMap {
  // 获取当前节点的internalEventHandlersKey这个属性值
  let elementListenerMap = (node: any)[internalEventHandlersKey];
  if (elementListenerMap === undefined) {
    elementListenerMap = (node: any)[internalEventHandlersKey] = new Map();
  }
  return elementListenerMap;
}

【事件存储】

这里进入listenToNativeEvent方法:

// react-dom/src/events/DOMPluginEventSystem.js

export function listenToNativeEvent(
  domEventName: DOMEventName, // 'change'、'click'、'focusin'等dependence
  isCapturePhaseListener: boolean,
  rootContainerElement: EventTarget,
  targetElement: Element | null,
  isPassiveListener?: boolean,
  listenerPriority?: EventPriority,
  eventSystemFlags?: EventSystemFlags = PLUGIN_EVENT_SYSTEM,
): void {
  let target = rootContainerElement; // div#root

  // 这边去获取上面提到的那个map对象
  const listenerMap = getEventListenerMap(target);

  // listenerMapKey返回的形式为“change_bubble | change_capture”;
  const listenerMapKey = getListenerMapKey(
    domEventName,
    isCapturePhaseListener,
  );
  // 判断listenerMap中是否存在listenerMapKey
  const listenerEntry = ((listenerMap.get(
    listenerMapKey,
  ): any): ElementListenerMapEntry | void);

  // 判断是否需要更新
  const shouldUpgrade = shouldUpgradeListener(listenerEntry, isPassiveListener);

  // 如果不存在当前事件,或者需要更新,进入判断
  if (listenerEntry === undefined || shouldUpgrade) {
    // ...

    // addTrappedEventListener内部就是做了:在target上进行事件监听,并返回dispatchEvent函数
    const listener = addTrappedEventListener(
      target,                 // DOM container
      domEventName,           // dependence
      eventSystemFlags,
      isCapturePhaseListener, // false
      false,
      isPassiveListener,      // undefined
      listenerPriority,
    );

    // 最终这个listenerMap会变成{onChange: null, change_bubble: {passive: isPassiveListener, listener}, click_bubble: {passive: isPassiveListener, listener}, focusin_bubble: {passive: isPassiveListener, listener}}
    listenerMap.set(listenerMapKey, {passive: isPassiveListener, listener});
  }
}

export function getListenerMapKey(
  domEventName: DOMEventName,
  capture: boolean,
): string {
  return `${domEventName}__${capture ? 'capture' : 'bubble'}`;
}

                                                              listenerMap的结构

【事件注册】

上面代码中,真正的事件监听操作的入口在addTrappedEventListener方法里:

// react-dom/src/events/DOMPluginEventSystem.js

function addTrappedEventListener(
  targetContainer: EventTarget,
  domEventName: DOMEventName, // dependence
  eventSystemFlags: EventSystemFlags,
  isCapturePhaseListener: boolean,
  isDeferredListenerForLegacyFBSupport?: boolean,
  isPassiveListener?: boolean,
  listenerPriority?: EventPriority,
): any => void {
  // 这段代码尤为重要,通过传入的domEventName获取当前事件的优先级,返回的是经过包装过的三类dispatchEvent事件
  // 分别为dispatchDiscreteEvent =>0 | dispatchUserBlockingUpdate =>1 | dispatchEvent=>2
  let listener = createEventListenerWrapperWithPriority(
    targetContainer,
    domEventName,
    eventSystemFlags,
    listenerPriority,
  );

  if (isCapturePhaseListener) {
    if (enableCreateEventHandleAPI && isPassiveListener !== undefined) {
      unsubscribeListener = addEventCaptureListenerWithPassiveFlag(
        targetContainer,
        domEventName,
        listener,
        isPassiveListener,
      );
    } else {
      // 入参listener:dispatchEvent;unsubscribeListener=dispatchEvent;
      unsubscribeListener = addEventCaptureListener(
        targetContainer,
        domEventName,
        listener,
      );
    }
  } else {
    if (enableCreateEventHandleAPI && isPassiveListener !== undefined) {
      unsubscribeListener = addEventBubbleListenerWithPassiveFlag(
        targetContainer,
        domEventName,
        listener,
        isPassiveListener,
      );
    } else {
      unsubscribeListener = addEventBubbleListener(
        targetContainer,
        domEventName,
        listener,
      );
    }
  }
  return unsubscribeListener;
}

// 函数:createEventListenerWrapperWithPriority
export function createEventListenerWrapperWithPriority(
  targetContainer: EventTarget,
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
  priority?: EventPriority,
): Function {
  const eventPriority =
    priority === undefined
      ? getEventPriorityForPluginSystem(domEventName)
      : priority;
  let listenerWrapper;
  switch (eventPriority) {
    case DiscreteEvent:
      listenerWrapper = dispatchDiscreteEvent;
      break;
    case UserBlockingEvent:
      a = dispatchUserBlockingUpdate;
      break;
    case ContinuousEvent:
    default:
      listenerWrapper = dispatchEvent;
      break;
  }
  return listenerWrapper.bind(
    null,
    domEventName,
    eventSystemFlags,
    targetContainer, // document
  );
}

上面的listener等于createEventListenerWrapperWithPriority的方法调用,该方法内部主要去判断了当前dependence事件的优先级,再对应到不同的dispatch方法,主要是三类(这边先略讲,和本次主题关系不大,主要和fiber的分片机制相关)。

最后,addEventBubbleListener和addEventCaptureListener这两个方法大家就很熟悉了:

export function addEventBubbleListener(
  target: EventTarget, // div#root
  eventType: string, // change
  listener: Function, // dispatchEvent
): Function {
  target.addEventListener(eventType, listener, false);
  return listener;
}

export function addEventCaptureListener(
  target: EventTarget,
  eventType: string,
  listener: Function,
): Function {
  // listener为dispatchEvent,不是真正的回调!!!
  target.addEventListener(eventType, listener, true);
  return listener;
}

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

总结:事件注册的流程,就是遍历props中的event,然后将事件和其依赖事件都挂载到**target上,当中所有的事件的回调函数走的都是dispatchEvent,并且相同类型的事件只会挂在一次。打个比方,如果我绑定一个onChange事件,那么react不仅仅只绑定一个onChange事件到****target****上,还会绑定许多依赖事件上去,如focus,blur,input等等,组件中声明的事件并不会保存起来,而仅仅是将事件类型以及dispatchEvent函数绑定到target**元素上,实现事件委派。

二、事件合成&事件分发&事件执行

1. 流程图

2. 流程细节

第一部分事件注册有讲事件绑定的时候,只是传了一个dispatchEvent进行事件委托,那当在一个dom节点上进行onChange时,怎么触发真正的回调呢?这边我们从dispatchEvent入手,来看一下进步原理:

// react-dom/rc/events/ReactDOMEventListener.js

export function dispatchEvent(
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
  targetContainer: EventTarget,
  nativeEvent: AnyNativeEvent,
): void {
  // ...
  dispatchEventForPluginEventSystem(
      domEventName,
      eventSystemFlags,
      nativeEvent,
      null,
      targetContainer,
  );
}

export function dispatchEventForPluginEventSystem(
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
  nativeEvent: AnyNativeEvent,
  targetInst: null | Fiber,
  targetContainer: EventTarget,
): void {
  // 批处理更新方法
  batchedEventUpdates(() =>
    dispatchEventsForPlugins(
      domEventName,
      eventSystemFlags,
      nativeEvent,
      ancestorInst,
      targetContainer,
    ),
  );
}

// 将全局变量isBatchingEventUpdates设置为true
export function batchedEventUpdates(fn, a, b) {
  if (isBatchingEventUpdates) {
    return fn(a, b);
  }
  isBatchingEventUpdates = true;
  try {
    // 这是个工具函数,返回fn(a, b);
    return batchedEventUpdatesImpl(fn, a, b);
  } finally {
    isBatchingEventUpdates = false;
    finishEventHandler();
  }
}

function dispatchEventsForPlugins(
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
  nativeEvent: AnyNativeEvent,
  targetInst: null | Fiber,
  targetContainer: EventTarget,
): void {
  const nativeEventTarget = getEventTarget(nativeEvent);
  const dispatchQueue: DispatchQueue = [];
  // 进行事件合成
  extractEvents(
    dispatchQueue, // []
    domEventName, // dependence
    targetInst,
    nativeEvent, // 原生事件
    nativeEventTarget, // 当前dom元素
    eventSystemFlags,
    targetContainer, // div#root
  );
  // 按顺序执行事件队列,此时dispatchQueue已经变成[onChange, [{instance, listener, currentTarget}, ...]]
  processDispatchQueue(dispatchQueue, eventSystemFlags);
}

dispatchEventsForPlugins方法是事件callback调用的核心。它主要做两件事情,一方面利用浏览器回传的原生事件构造出React合成事件另一方面采用队列的方式处理events。先看如何构造合成事件:

【事件合成】

extractEvents这个方法就是调用各种插件来创建相应函数的合成事件,一共有6种插件,这边用到了5个。事件的合成,冒泡的处理以及事件回调的查找都是在合成阶段完成的。

// react-dom/rc/events/ReactDOMEventListener.js

function extractEvents(
  dispatchQueue: DispatchQueue, // 初始为[]
  domEventName: DOMEventName, // dependence
  targetInst: null | Fiber, // 
  nativeEvent: AnyNativeEvent, // 原生事件
  nativeEventTarget: null | EventTarget, // 当前dom元素
  eventSystemFlags: EventSystemFlags,
  targetContainer: EventTarget,
) {
  SimpleEventPlugin.extractEvents(
    dispatchQueue,
    domEventName,
    targetInst,
    nativeEvent,
    nativeEventTarget,
    eventSystemFlags,
    targetContainer,
  );
  const shouldProcessPolyfillPlugins = (eventSystemFlags & SHOULD_NOT_PROCESS_POLYFILL_EVENT_PLUGINS) === 0;
  if (shouldProcessPolyfillPlugins) {
    EnterLeaveEventPlugin.extractEvents(
      ...
    );
    ChangeEventPlugin.extractEvents(
      ...
    );
    SelectEventPlugin.extractEvents(
      ...
    );
    BeforeInputEventPlugin.extractEvents(
     ...
    );
  }
}

我们以ChangeEventPlugin插件举例:

// react-dom/src/client/events/ChangeEventPlugin.js

function extractEvents(
  dispatchQueue: DispatchQueue,
  domEventName: DOMEventName,
  targetInst: null | Fiber,
  nativeEvent: AnyNativeEvent,
  nativeEventTarget: null | EventTarget,
  eventSystemFlags: EventSystemFlags,
  targetContainer: null | EventTarget,
) {
  const targetNode = targetInst ? getNodeFromInstance(targetInst) : window;

  let getTargetInstFunc, handleEventFunc;
  // 这边判断当前的节点符不符合当前插件创建相应合成事件的要求
  if (shouldUseChangeEvent(targetNode)) {
    getTargetInstFunc = getTargetInstForChangeEvent;
  } else if (isTextInputElement(((targetNode: any): HTMLElement))) {
    if (isInputEventSupported) {
      getTargetInstFunc = getTargetInstForInputOrChangeEvent;
    } else {
      getTargetInstFunc = getTargetInstForInputEventPolyfill;
      handleEventFunc = handleEventsForInputEventPolyfill;
    }
  } else if (shouldUseClickEvent(targetNode)) {
    getTargetInstFunc = getTargetInstForClickEvent;
  }

  if (getTargetInstFunc) {
    const inst = getTargetInstFunc(domEventName, targetInst);
    if (inst) {
      // dispatchQueue一开始为[]
      createAndAccumulateChangeEvent(
        dispatchQueue,
        inst,
        nativeEvent,
        nativeEventTarget,
      );
      return;
    }
  }

  if (handleEventFunc) {
    handleEventFunc(domEventName, targetNode, targetInst);
  }

紧接着我们来看下createAndAccumulateChangeEvent方法:

// react-dom/src/client/events/plugins/ChangeEventPlugin.js
function createAndAccumulateChangeEvent(
  dispatchQueue,
  inst,
  nativeEvent,
  target,
) {
  // 创建合成事件
  const event = new SyntheticEvent('onChange', null, nativeEvent, target);
  event.type = 'change';
  enqueueStateRestore(((target: any): Node));
  // 事件分发,给dispatchQueue赋值
  accumulateTwoPhaseListeners(inst, dispatchQueue, event);
}

【事件分发】

// react-dom/src/client/events/DOMPluginEventSystem.js

export function accumulateTwoPhaseListeners(
  targetFiber: Fiber | null, 
  dispatchQueue: DispatchQueue, // []
  event: ReactSyntheticEvent, // onChange合成事件
): void {
  const bubbled = event._reactName; // 就是“onChange”
  const captured = bubbled !== null ? bubbled + 'Capture' : null; // 就是“onChangeCapture”
  const listeners: Array<DispatchListener> = [];
  let instance = targetFiber;

  // 这边向上查找到所有当前类型事件的回调函数,重要!!!
  while (instance !== null) {
    const {stateNode, tag} = instance;
    if (tag === HostComponent && stateNode !== null) {
      const currentTarget = stateNode;
      if (captured !== null) {
        // 返回当前节点的回调函数
        const captureListener = getListener(instance, captured);
        if (captureListener != null) {
          // 捕获,插入数组头部
          listeners.unshift(
            // 工具函数,返回对象{instance, listener, currentTarget}
            createDispatchListener(instance, captureListener, currentTarget),
          );
        }
      }
      if (bubbled !== null) {
        // 返回当前节点的回调函数
        const bubbleListener = getListener(instance, bubbled);
        if (bubbleListener != null) {
          // 冒泡,插入数组尾部
          listeners.push(
            // 工具函数,返回对象{instance, listener, currentTarget}
            createDispatchListener(instance, bubbleListener, currentTarget),
          );
        }
      }
    }
    instance = instance.return;
  }

  // listeners即某一类合成事件的所有回调函数的集合,[{instance, listener, currentTarget}, ...]
  if (listeners.length !== 0) {
    // createDispatchEntry返回的是对象{event, listeners};
    // dispatchQueue最后为[{event, listeners}, ...], 即[{onChange, [{instance, listener, currentTarget}, ...]}, ...]
    dispatchQueue.push(createDispatchEntry(event, listeners));
  }
}

export default function getListener(
  inst: Fiber, // 当前实例
  registrationName: string, // “onChange”
): Function | null {
  ...
  // 返回dom上的props
  const props = getFiberCurrentPropsFromNode(stateNode);
  if (props === null) {
    // Work in progress.
    return null;
  }
  // 获取到当前事件的回调函数
  const listener = props[registrationName]
  return listener;
}

以上就进行了事件合成以及拿到了所有回调函数组成的队列。这边的事件合成和16.8版本即以前有很大的不同,本版本已经去掉了“事件池“的概念,每次的合成事件都是重新new的!!!接下来就到事件执行环节了:

【事件执行】

// react-dom/rc/events/DOMPluginEventSystem.js

export 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);
  }
  rethrowCaughtError();
}
// react-dom/rc/events/DOMPluginEventSystem.js

function processDispatchQueueItemsInOrder(
  event: ReactSyntheticEvent,  // "onChange"合成事件
  dispatchListeners: Array<DispatchListener>,
  inCapturePhase: boolean,
): void {
  let previousInstance;
  if (inCapturePhase) {
    // 遍历dispatchListeners
    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++) {
      // 取出每个回调函数所在的对象里的instance, currentTarget, listener
      const {instance, currentTarget, listener} = dispatchListeners[i];
      if (instance !== previousInstance && event.isPropagationStopped()) {
        return;
      }
      // 最终执行回调
      executeDispatch(event, listener, currentTarget);
      previousInstance = instance;
    }
  }
}

function executeDispatch(
  event: ReactSyntheticEvent, // onChange
  listener: Function, // 对应的回调函数
  currentTarget: EventTarget,
): void {
  // "onChange"
  const type = event.type || 'unknown-event'; 
  // 将当前dom元素赋值给合成事件的currentTarget
  event.currentTarget = currentTarget;
  // 执行回调函数,listener为回调函数, event为合成事件,最后执行listener(event)这个方法调用
  // 这样就回调到了我们在JSX中注册的callback。比如onClick={(event) => {console.log(1)}}
  // 现在就明白了callback怎么被调用的,以及event参数怎么传入callback里面的了
  invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event);
  event.currentTarget = null;
}
// shared/invokeGuardedCallbackImpl.js

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 {
    //最后就是在这里执行的回调函数
    func.apply(context, funcArgs);
  } catch (error) {
    this.onError(error);
  }
}

这样就完成了事件执行了。