React 合成事件( Synthetic events)机制浅析

280 阅读4分钟

React 合成事件Synthetic events是 React 框架中用于优化事件处理的核心机制,是一个完全符合 DOM3的事件系统:

  • 跨浏览器一致性:通过统一封装,抹平了不同浏览器的事件系统差异(比如IE11只支持冒泡、阻止冒泡是使用event.cancelBubble = true),有统一的 API 方便使用
  • 性能优化:大部分使用事件委托减少了监听器数量,同时了避免了频繁监听、销毁事件的开销(滚动事件依然是单独元素监听)

合成事件的机制

createRoot创建根节点时,通过事件委托来监听所有事件,后续在通过分发来触发我们代码中的回调,整个事件的流程,React 使用插件机制来实现:

1. 事件的注册

React 在初始化时,所有的事件注册在引入DOMPluginEventSystem.js全局执行:

//src/react/packages/react-dom/src/events/DOMPluginEventSystem.js
SimpleEventPlugin.registerEvents();//最终触发registerDirectEvent
EnterLeaveEventPlugin.registerEvents();
//src/react/packages/react-dom/src/events/EventRegistry.js
// 全局变量、全局监听也是使用allNativeEvents
export const allNativeEvents: Set<DOMEventName> = new Set();

//registerEvents最终触发registerDirectEvent
export function registerDirectEvent(
  registrationName: string,
  dependencies: Array<DOMEventName>,
) {
  for (let i = 0; i < dependencies.length; i++) {
    allNativeEvents.add(dependencies[i]);
  }
}

React 在注册时会将事件分为

  • 离散事件:立即执行,抢占式调度(如click)
  • 用户阻塞事件:微任务队列调度,避免阻塞渲染(如drag)
  • 连续事件:宏任务队列调度,保证流畅性(如scroll)

通过createEventListenerWrapperWithPriority分配不同调度策略:

function createEventListenerWrapperWithPriority(...) {
  const eventPriority = getEventPriorityForPluginSystem(domEventName); // 获取优先级
  switch (eventPriority) {
    case DiscreteEvent:
      listenerWrapper = dispatchDiscreteEvent;  // 离散事件策略
      break;
    case UserBlockingEvent:
      listenerWrapper = dispatchUserBlockingUpdate; // 用户阻塞策略
      break;
    case ContinuousEvent:
      listenerWrapper = dispatchEvent;  // 连续事件策略
      break;
  }
  return listenerWrapper.bind(...);
}

2. 顶层事件监听和分发

React在应用根容器(React 17+)或document(React 16及之前)上统一绑定事件监听

  • 遍历allNativeEvents,为每个事件类型绑定捕获/冒泡阶段的监听器;
//src/react/packages/react-dom/src/client/ReactDOMRoot.js
export function listenToAllSupportedEvents(rootContainerElement: EventTarget) {
  allNativeEvents.forEach(domEventName => {
    listenToNativeEvent(
      domEventName,
      true,
      ((rootContainerElement: any): Element),
      null,
    );
  });
}
  • 事件代理函数dispatchEvent作为统一回调,通过事件冒泡/捕获路径触发插件处理逻辑。
//src/react/packages/react-dom/src/events/ReactDOMEventListener.js
export function dispatchEvent(
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
  targetContainer: EventTarget,
  nativeEvent: AnyNativeEvent,
): void { 
  //...省略多余代码
  //最终都会触发dispatchEvent -> dispatchEventForPluginEventSystem
  dispatchEventForPluginEventSystem(
    domEventName,
    eventSystemFlags,
    nativeEvent,
    null,
    targetContainer,
  );
}

3. 事件的提取

当原生事件触发时,React通过插件分层处理机制生成合成事件:

  • 事件分发入口dispatchEventForPluginEventSystem核心是调用dispatchEventsForPlugins,进而触发插件的extractEvents()方法收集所有事件回调,processDispatchQueue执行所有回调
//src/react/packages/react-dom/src/events/DOMPluginEventSystem.js
function dispatchEventsForPlugins(
  domEventName: DOMEventName,
): void {
  const dispatchQueue: DispatchQueue = []; //item 格式 {event, listeners}
  //解析事件,获取 listeners 和 创建每个listener的 event
  extractEvents(
    dispatchQueue,
    domEventName,
  );
  //触发事件
  processDispatchQueue(dispatchQueue, eventSystemFlags);
}
  • 递归Fiber树收集处理函数extractEvents()会沿组件树向上遍历,收集所有绑定的事件回调;
//src/react/packages/react-dom/src/events/DOMPluginEventSystem.js
export function accumulateSinglePhaseListeners(
  targetFiber: Fiber | null,
  reactName: string | null,
  nativeEventType: string,
  inCapturePhase: boolean,
  accumulateTargetOnly: boolean,
): Array<DispatchListener> {  
  const captureName = reactName !== null ? reactName + 'Capture' : null;
  const reactEventName = inCapturePhase ? captureName : reactName;
  const listeners: Array<DispatchListener> = [];

  let instance = targetFiber;
  while (instance !== null) {

      // Standard React on* listeners, i.e. onClick or onClickCapture
      if (reactEventName !== null) {
        //getListener 从 props中获取回调函数
        const listener = getListener(instance, reactEventName);
        if (listener != null) {
          listeners.push(
            createDispatchListener(instance, listener, lastHostComponent),
          );
        }
      }
    
    //向上遍历
    instance = instance.return; 
  }
  
  return listeners;
}
  • 合成事件对象构造:插件对有回调函数的创建SyntheticEvent实例,封装原生事件属性和跨浏览器兼容逻辑。
//src/react/packages/react-dom/src/events/plugins/SimpleEventPlugin.js
function extractEvents(
  dispatchQueue: DispatchQueue,
  domEventName: DOMEventName,
  ...args,
): void {
    const listeners = accumulateSinglePhaseListeners(
      targetInst,
      reactName,
      nativeEvent.type,
      inCapturePhase,
      accumulateTargetOnly,
    );
    if (listeners.length > 0) {
      // Intentionally create event lazily.
      const event = new SyntheticEventCtor(
        reactName,
        reactEventType,
        null,
        nativeEvent,
        nativeEventTarget,
      );
      dispatchQueue.push({event, listeners});
    }
}

4. 事件的分发

最终都会通过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;
    }
  }
}

总结

React通过插件注册-顶层绑定-分层处理的三层架构,将原生事件转化为标准化合成事件。其核心价值在于:

  1. 跨浏览器一致性:插件屏蔽底层差异(如keyCode vs key);
  2. 性能优化:单一监听+对象复用减少资源消耗;
  3. 逻辑解耦:插件机制支持灵活扩展新事件类型