Skip to content

合成事件系统原理 - React事件机制深度解析

1. 合成事件概述

1.1 什么是合成事件

typescript
const syntheticEventConcept = {
  definition: 'React对原生DOM事件的跨浏览器包装器',
  
  特点: {
    跨浏览器: '抹平浏览器差异',
    性能优化: '事件委托到根节点',
    统一接口: '一致的事件API',
    池化管理: '事件对象复用(React 17前)'
  },
  
  与原生事件的区别: {
    命名: 'onClick vs onclick (驼峰命名)',
    阻止默认: 'e.preventDefault() (不能return false)',
    this绑定: '需要手动绑定或使用箭头函数',
    事件委托: '委托到根节点而非目标元素',
    对象池: '事件对象会被复用(17前)'
  }
};

1.2 为什么需要合成事件

typescript
const whySyntheticEvents = {
  跨浏览器兼容: {
    问题: '不同浏览器事件API差异',
    example: `
      // IE: attachEvent
      element.attachEvent('onclick', handler);
      
      // 标准: addEventListener
      element.addEventListener('click', handler);
      
      // React: 统一接口
      <div onClick={handler} />
    `,
    解决: 'React统一处理兼容性'
  },
  
  性能优化: {
    问题: '每个元素绑定事件占用内存',
    example: `
      // 原生: 1000个元素1000个监听器
      items.forEach(item => {
        item.addEventListener('click', handler);
      });
      
      // React: 只在根节点一个监听器
      <div>{items.map(item => <Item onClick={handler} />)}</div>
    `,
    解决: '事件委托到根节点'
  },
  
  事件池: {
    问题: '频繁创建事件对象消耗性能',
    solution: '复用事件对象(17前)',
    注意: 'React 17+废弃了事件池'
  },
  
  统一管理: {
    benefit: [
      '事件优先级控制',
      '批量更新',
      '事件重放',
      '错误处理'
    ]
  }
};

1.3 合成事件类型体系

typescript
// SyntheticEvent基类
interface BaseSyntheticEvent<E = object, C = any, T = any> {
  nativeEvent: E;
  currentTarget: C;
  target: T;
  bubbles: boolean;
  cancelable: boolean;
  defaultPrevented: boolean;
  eventPhase: number;
  isTrusted: boolean;
  preventDefault(): void;
  isDefaultPrevented(): boolean;
  stopPropagation(): void;
  isPropagationStopped(): boolean;
  persist(): void;
  timeStamp: number;
  type: string;
}

// 具体事件类型
interface SyntheticMouseEvent<T = Element> extends BaseSyntheticEvent<MouseEvent, EventTarget & T, EventTarget> {
  altKey: boolean;
  button: number;
  buttons: number;
  clientX: number;
  clientY: number;
  ctrlKey: boolean;
  metaKey: boolean;
  pageX: number;
  pageY: number;
  relatedTarget: EventTarget | null;
  screenX: number;
  screenY: number;
  shiftKey: boolean;
}

interface SyntheticKeyboardEvent<T = Element> extends BaseSyntheticEvent<KeyboardEvent, EventTarget & T, EventTarget> {
  altKey: boolean;
  charCode: number;
  ctrlKey: boolean;
  key: string;
  keyCode: number;
  locale: string;
  location: number;
  metaKey: boolean;
  repeat: boolean;
  shiftKey: boolean;
  which: number;
}

interface SyntheticInputEvent<T = Element> extends BaseSyntheticEvent<InputEvent, EventTarget & T, EventTarget> {
  data: string | null;
  dataTransfer: DataTransfer | null;
  inputType: string;
  isComposing: boolean;
}

// ... 更多事件类型

2. 事件注册

2.1 事件插件系统

typescript
// 事件插件接口
interface EventPlugin {
  eventTypes: Record<string, EventConfig>;
  extractEvents(
    topLevelType: TopLevelType,
    targetInst: Fiber | null,
    nativeEvent: Event,
    nativeEventTarget: EventTarget | null
  ): SyntheticEvent | null;
}

// 简单事件插件
const SimpleEventPlugin: EventPlugin = {
  eventTypes: {
    click: {
      phasedRegistrationNames: {
        bubbled: 'onClick',
        captured: 'onClickCapture'
      },
      dependencies: ['click']
    },
    
    mouseenter: {
      registrationName: 'onMouseEnter',
      dependencies: ['mouseout', 'mouseover']
    },
    
    change: {
      phasedRegistrationNames: {
        bubbled: 'onChange',
        captured: 'onChangeCapture'
      },
      dependencies: [
        'blur',
        'change',
        'click',
        'focus',
        'input',
        'keydown',
        'keyup',
        'selectionchange'
      ]
    }
  },
  
  extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget) {
    const dispatchConfig = this.eventTypes[topLevelType];
    
    if (!dispatchConfig) {
      return null;
    }
    
    // 创建合成事件
    const EventConstructor = getEventConstructor(topLevelType);
    const event = EventConstructor.getPooled(
      dispatchConfig,
      targetInst,
      nativeEvent,
      nativeEventTarget
    );
    
    return event;
  }
};

// 变化事件插件
const ChangeEventPlugin: EventPlugin = {
  eventTypes: {
    change: {
      phasedRegistrationNames: {
        bubbled: 'onChange',
        captured: 'onChangeCapture'
      },
      dependencies: [
        'blur', 'change', 'click', 'focus',
        'input', 'keydown', 'keyup', 'selectionchange'
      ]
    }
  },
  
  extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget) {
    // 特殊处理change事件
    const event = createChangeEvent(/* ... */);
    return event;
  }
};

// 选择事件插件
const SelectEventPlugin: EventPlugin = {
  eventTypes: {
    select: {
      phasedRegistrationNames: {
        bubbled: 'onSelect',
        captured: 'onSelectCapture'
      },
      dependencies: [
        'focusout',
        'contextmenu',
        'dragend',
        'focusin',
        'keydown',
        'keyup',
        'mousedown',
        'mouseup',
        'selectionchange'
      ]
    }
  },
  
  extractEvents(/* ... */) {
    // 处理文本选择
    return createSelectEvent(/* ... */);
  }
};

2.2 注册事件监听

typescript
// React 17+事件注册到root
function listenToAllSupportedEvents(rootContainerElement: EventTarget) {
  // 只注册一次
  if ((rootContainerElement as any)[rootListenersMarkerKey]) {
    return;
  }
  (rootContainerElement as any)[rootListenersMarkerKey] = true;
  
  // 遍历所有支持的事件
  allNativeEvents.forEach(domEventName => {
    if (!nonDelegatedEvents.has(domEventName)) {
      // 委托事件
      listenToNativeEvent(
        domEventName,
        false, // 冒泡
        rootContainerElement
      );
      listenToNativeEvent(
        domEventName,
        true,  // 捕获
        rootContainerElement
      );
    }
  });
  
  // 非委托事件(如scroll, focus)
  const ownerDocument = rootContainerElement.ownerDocument || rootContainerElement;
  nonDelegatedEvents.forEach(domEventName => {
    listenToNativeEvent(
      domEventName,
      false,
      ownerDocument
    );
  });
}

function listenToNativeEvent(
  domEventName: string,
  isCapturePhaseListener: boolean,
  target: EventTarget
) {
  let eventSystemFlags = 0;
  
  if (isCapturePhaseListener) {
    eventSystemFlags |= IS_CAPTURE_PHASE;
  }
  
  addTrappedEventListener(
    target,
    domEventName,
    eventSystemFlags,
    isCapturePhaseListener
  );
}

function addTrappedEventListener(
  targetContainer: EventTarget,
  domEventName: string,
  eventSystemFlags: EventSystemFlags,
  isCapturePhaseListener: boolean
) {
  // 创建监听器
  const listener = createEventListenerWrapperWithPriority(
    targetContainer,
    domEventName,
    eventSystemFlags
  );
  
  // 添加监听
  if (isCapturePhaseListener) {
    targetContainer.addEventListener(domEventName, listener, true);
  } else {
    targetContainer.addEventListener(domEventName, listener, false);
  }
}

2.3 优先级包装

typescript
function createEventListenerWrapperWithPriority(
  targetContainer: EventTarget,
  domEventName: string,
  eventSystemFlags: EventSystemFlags
): (event: Event) => void {
  const eventPriority = getEventPriorityForPluginSystem(domEventName);
  let listenerWrapper;
  
  switch (eventPriority) {
    case DiscreteEvent:
      listenerWrapper = dispatchDiscreteEvent;
      break;
    case UserBlockingEvent:
      listenerWrapper = dispatchUserBlockingUpdate;
      break;
    case ContinuousEvent:
    default:
      listenerWrapper = dispatchEvent;
      break;
  }
  
  return listenerWrapper.bind(
    null,
    domEventName,
    eventSystemFlags,
    targetContainer
  );
}

function getEventPriorityForPluginSystem(domEventName: string): EventPriority {
  switch (domEventName) {
    // 离散事件
    case 'click':
    case 'keydown':
    case 'keyup':
    case 'input':
    case 'change':
    case 'submit':
      return DiscreteEvent;
    
    // 用户阻塞事件
    case 'drag':
    case 'dragenter':
    case 'dragleave':
    case 'dragover':
    case 'mouseenter':
    case 'mouseleave':
    case 'mousemove':
    case 'mouseout':
    case 'mouseover':
    case 'pointerenter':
    case 'pointerleave':
    case 'pointermove':
    case 'pointerout':
    case 'pointerover':
    case 'scroll':
    case 'touchmove':
    case 'wheel':
      return ContinuousEvent;
    
    // 默认优先级
    default:
      return ContinuousEvent;
  }
}

3. 事件触发

3.1 事件分发

typescript
function dispatchEvent(
  domEventName: string,
  eventSystemFlags: EventSystemFlags,
  targetContainer: EventTarget,
  nativeEvent: Event
) {
  // 获取目标Fiber
  const nativeEventTarget = getEventTarget(nativeEvent);
  const targetInst = getClosestInstanceFromNode(nativeEventTarget);
  
  // 批量更新上下文
  batchedEventUpdates(() => {
    dispatchEventsForPlugins(
      domEventName,
      eventSystemFlags,
      nativeEvent,
      targetInst,
      targetContainer
    );
  });
}

function dispatchEventsForPlugins(
  domEventName: string,
  eventSystemFlags: EventSystemFlags,
  nativeEvent: Event,
  targetInst: null | Fiber,
  targetContainer: EventTarget
) {
  const nativeEventTarget = getEventTarget(nativeEvent);
  
  // 收集事件路径
  const dispatchQueue: DispatchQueue = [];
  
  // 提取事件
  extractEvents(
    dispatchQueue,
    domEventName,
    targetInst,
    nativeEvent,
    nativeEventTarget,
    eventSystemFlags,
    targetContainer
  );
  
  // 处理事件队列
  processDispatchQueue(dispatchQueue, eventSystemFlags);
}

3.2 提取事件

typescript
function extractEvents(
  dispatchQueue: DispatchQueue,
  domEventName: string,
  targetInst: null | Fiber,
  nativeEvent: Event,
  nativeEventTarget: null | EventTarget,
  eventSystemFlags: EventSystemFlags,
  targetContainer: EventTarget
) {
  // SimpleEventPlugin提取
  const simpleEvent = SimpleEventPlugin.extractEvents(
    domEventName,
    targetInst,
    nativeEvent,
    nativeEventTarget,
    eventSystemFlags,
    targetContainer
  );
  
  if (simpleEvent) {
    dispatchQueue.push({
      event: simpleEvent,
      listeners: accumulateSinglePhaseListeners(
        targetInst,
        simpleEvent.type,
        nativeEvent.type
      )
    });
  }
  
  // 其他插件...
}

function accumulateSinglePhaseListeners(
  targetFiber: Fiber | null,
  reactName: string,
  nativeEventType: string
): Array<DispatchListener> {
  const captureName = reactName + 'Capture';
  const listeners: Array<DispatchListener> = [];
  
  let instance = targetFiber;
  
  while (instance !== null) {
    const { stateNode, tag } = instance;
    
    // HostComponent(原生DOM)
    if (tag === HostComponent && stateNode !== null) {
      const currentTarget = stateNode;
      
      // 捕获阶段监听器
      const captureListener = getListener(instance, captureName);
      if (captureListener != null) {
        listeners.unshift({
          instance,
          listener: captureListener,
          currentTarget
        });
      }
      
      // 冒泡阶段监听器
      const bubbleListener = getListener(instance, reactName);
      if (bubbleListener != null) {
        listeners.push({
          instance,
          listener: bubbleListener,
          currentTarget
        });
      }
    }
    
    instance = instance.return;
  }
  
  return listeners;
}

function getListener(inst: Fiber, registrationName: string): Function | null {
  const stateNode = inst.stateNode;
  if (stateNode === null) {
    return null;
  }
  
  const props = getFiberCurrentPropsFromNode(stateNode);
  if (props === null) {
    return null;
  }
  
  const listener = props[registrationName];
  
  if (listener && typeof listener !== 'function') {
    throw new Error(
      `Expected \`${registrationName}\` listener to be a function, ` +
      `instead got a value of \`${typeof listener}\` type.`
    );
  }
  
  return listener;
}

3.3 处理事件队列

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

function processDispatchQueueItemsInOrder(
  event: SyntheticEvent,
  dispatchListeners: Array<DispatchListener>,
  inCapturePhase: boolean
) {
  if (inCapturePhase) {
    // 捕获阶段: 从根到目标(正序)
    for (let i = dispatchListeners.length - 1; i >= 0; i--) {
      const { instance, currentTarget, listener } = dispatchListeners[i];
      
      if (event.isPropagationStopped()) {
        return;
      }
      
      executeDispatch(event, listener, currentTarget);
    }
  } else {
    // 冒泡阶段: 从目标到根(逆序)
    for (let i = 0; i < dispatchListeners.length; i++) {
      const { instance, currentTarget, listener } = dispatchListeners[i];
      
      if (event.isPropagationStopped()) {
        return;
      }
      
      executeDispatch(event, listener, currentTarget);
    }
  }
}

function executeDispatch(
  event: SyntheticEvent,
  listener: Function,
  currentTarget: EventTarget
) {
  const type = event.type || 'unknown-event';
  event.currentTarget = currentTarget;
  
  try {
    listener(event);
  } catch (error) {
    // 错误处理
    console.error(
      `Error in ${type} handler: `,
      error
    );
  }
  
  event.currentTarget = null;
}

4. 合成事件对象

4.1 SyntheticEvent构造

typescript
// 合成事件基类
function SyntheticEvent(
  dispatchConfig: EventConfig,
  targetInst: Fiber,
  nativeEvent: Event,
  nativeEventTarget: EventTarget
) {
  this.dispatchConfig = dispatchConfig;
  this._targetInst = targetInst;
  this.nativeEvent = nativeEvent;
  this._dispatchListeners = null;
  this._dispatchInstances = null;
  
  // 复制原生事件属性
  const Interface = this.constructor.Interface;
  for (const propName in Interface) {
    if (!Interface.hasOwnProperty(propName)) {
      continue;
    }
    
    const normalize = Interface[propName];
    if (normalize) {
      this[propName] = normalize(nativeEvent);
    } else {
      if (propName === 'target') {
        this.target = nativeEventTarget;
      } 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;
}

// 方法
Object.assign(SyntheticEvent.prototype, {
  preventDefault() {
    this.defaultPrevented = true;
    const event = this.nativeEvent;
    
    if (!event) {
      return;
    }
    
    if (event.preventDefault) {
      event.preventDefault();
    } else {
      event.returnValue = false;
    }
    
    this.isDefaultPrevented = functionThatReturnsTrue;
  },
  
  stopPropagation() {
    const event = this.nativeEvent;
    
    if (!event) {
      return;
    }
    
    if (event.stopPropagation) {
      event.stopPropagation();
    } else {
      event.cancelBubble = true;
    }
    
    this.isPropagationStopped = functionThatReturnsTrue;
  },
  
  persist() {
    // React 17前用于保持事件对象
    this.isPersistent = functionThatReturnsTrue;
  },
  
  isPersistent: functionThatReturnsFalse,
  
  destructor() {
    const Interface = this.constructor.Interface;
    
    for (const propName in Interface) {
      this[propName] = null;
    }
    
    this.dispatchConfig = null;
    this._targetInst = null;
    this.nativeEvent = null;
    this.isDefaultPrevented = functionThatReturnsFalse;
    this.isPropagationStopped = functionThatReturnsFalse;
    this._dispatchListeners = null;
    this._dispatchInstances = null;
  }
});

// 接口定义
SyntheticEvent.Interface = {
  type: null,
  target: null,
  currentTarget: null,
  eventPhase: null,
  bubbles: null,
  cancelable: null,
  timeStamp: (event: Event) => event.timeStamp || Date.now(),
  defaultPrevented: null,
  isTrusted: null
};

4.2 具体事件类型

typescript
// 鼠标事件
function SyntheticMouseEvent(
  dispatchConfig,
  targetInst,
  nativeEvent,
  nativeEventTarget
) {
  return SyntheticEvent.call(
    this,
    dispatchConfig,
    targetInst,
    nativeEvent,
    nativeEventTarget
  );
}

SyntheticMouseEvent.Interface = {
  ...SyntheticEvent.Interface,
  screenX: null,
  screenY: null,
  clientX: null,
  clientY: null,
  pageX: null,
  pageY: null,
  ctrlKey: null,
  shiftKey: null,
  altKey: null,
  metaKey: null,
  getModifierState: getEventModifierState,
  button: null,
  buttons: null,
  relatedTarget: (event: MouseEvent) => {
    return event.relatedTarget ||
      (event.fromElement === event.srcElement
        ? event.toElement
        : event.fromElement);
  },
  movementX: (event: MouseEvent) => {
    if ('movementX' in event) {
      return event.movementX;
    }
    const screenX = previousScreenX;
    previousScreenX = event.screenX;
    return isFirst
      ? 0
      : event.screenX - screenX;
  },
  movementY: (event: MouseEvent) => {
    if ('movementY' in event) {
      return event.movementY;
    }
    const screenY = previousScreenY;
    previousScreenY = event.screenY;
    return isFirst
      ? 0
      : event.screenY - screenY;
  }
};

// 键盘事件
function SyntheticKeyboardEvent(/* ... */) {
  return SyntheticEvent.call(this, /* ... */);
}

SyntheticKeyboardEvent.Interface = {
  ...SyntheticEvent.Interface,
  key: getEventKey,
  code: null,
  location: null,
  ctrlKey: null,
  shiftKey: null,
  altKey: null,
  metaKey: null,
  repeat: null,
  locale: null,
  getModifierState: getEventModifierState,
  charCode: (event: KeyboardEvent) => {
    if (event.type === 'keypress') {
      return getEventCharCode(event);
    }
    return 0;
  },
  keyCode: (event: KeyboardEvent) => {
    if (event.type === 'keydown' || event.type === 'keyup') {
      return event.keyCode;
    }
    return 0;
  },
  which: (event: KeyboardEvent) => {
    if (event.type === 'keypress') {
      return getEventCharCode(event);
    }
    if (event.type === 'keydown' || event.type === 'keyup') {
      return event.keyCode;
    }
    return 0;
  }
};

5. 事件池(React 17前)

5.1 对象池实现

typescript
// 事件对象池
const EVENT_POOL_SIZE = 10;

function addEventPoolingTo(EventConstructor: Function) {
  EventConstructor.eventPool = [];
  EventConstructor.getPooled = getPooledEvent;
  EventConstructor.release = releasePooledEvent;
}

function getPooledEvent(
  dispatchConfig: EventConfig,
  targetInst: Fiber,
  nativeEvent: Event,
  nativeEventTarget: EventTarget
) {
  const EventConstructor = this;
  
  if (EventConstructor.eventPool.length) {
    // 从池中获取
    const instance = EventConstructor.eventPool.pop();
    EventConstructor.call(
      instance,
      dispatchConfig,
      targetInst,
      nativeEvent,
      nativeEventTarget
    );
    return instance;
  }
  
  // 创建新实例
  return new EventConstructor(
    dispatchConfig,
    targetInst,
    nativeEvent,
    nativeEventTarget
  );
}

function releasePooledEvent(event: SyntheticEvent) {
  const EventConstructor = this;
  
  // 清理事件对象
  event.destructor();
  
  // 放回池中
  if (EventConstructor.eventPool.length < EVENT_POOL_SIZE) {
    EventConstructor.eventPool.push(event);
  }
}

// 应用到所有事件类型
addEventPoolingTo(SyntheticEvent);
addEventPoolingTo(SyntheticMouseEvent);
addEventPoolingTo(SyntheticKeyboardEvent);
// ... 其他事件类型

5.2 persist方法

typescript
// 使用persist保持事件对象
function handleClick(e: SyntheticMouseEvent) {
  // React 17前: 异步访问事件会报错
  setTimeout(() => {
    console.log(e.type); // Warning: 事件已被回收
  }, 100);
  
  // 使用persist保持
  e.persist();
  setTimeout(() => {
    console.log(e.type); // 正常工作
  }, 100);
}

// React 17+: 不再需要persist
function handleClick17(e: SyntheticMouseEvent) {
  setTimeout(() => {
    console.log(e.type); // 正常工作
  }, 100);
}

6. 特殊事件处理

6.1 Change事件

typescript
// Change事件特殊处理
function extractChangeEvent(
  dispatchQueue: DispatchQueue,
  domEventName: string,
  targetInst: Fiber | null,
  nativeEvent: Event,
  nativeEventTarget: EventTarget | null
) {
  // 不同元素的change行为不同
  const targetNode = targetInst ? targetInst.stateNode : null;
  
  if (!targetNode) {
    return;
  }
  
  const nodeName = targetNode.nodeName && targetNode.nodeName.toLowerCase();
  
  let handler;
  switch (nodeName) {
    case 'input':
      if (targetNode.type === 'checkbox' || targetNode.type === 'radio') {
        handler = handleInputChange;
      } else {
        handler = handleInputValueChange;
      }
      break;
    
    case 'textarea':
      handler = handleTextareaChange;
      break;
    
    case 'select':
      handler = handleSelectChange;
      break;
    
    default:
      return;
  }
  
  const event = handler(
    domEventName,
    targetInst,
    nativeEvent,
    nativeEventTarget
  );
  
  if (event) {
    dispatchQueue.push({
      event,
      listeners: accumulateSinglePhaseListeners(
        targetInst,
        'onChange',
        nativeEvent.type
      )
    });
  }
}

// input change处理
function handleInputValueChange(
  domEventName: string,
  targetInst: Fiber,
  nativeEvent: Event,
  nativeEventTarget: EventTarget
) {
  // 跟踪值变化
  const node = nativeEventTarget as HTMLInputElement;
  
  if (
    domEventName === 'input' ||
    domEventName === 'change'
  ) {
    return createChangeEvent(
      domEventName,
      targetInst,
      nativeEvent,
      nativeEventTarget
    );
  }
  
  return null;
}

6.2 Focus和Blur事件

typescript
// Focus/Blur事件处理
function extractFocusEvent(
  dispatchQueue: DispatchQueue,
  domEventName: string,
  targetInst: Fiber | null,
  nativeEvent: Event,
  nativeEventTarget: EventTarget | null
) {
  // React使用focusin/focusout替代focus/blur
  // 因为focus/blur不冒泡
  
  if (
    domEventName === 'focusin' ||
    domEventName === 'focusout'
  ) {
    const event = createFocusEvent(
      domEventName === 'focusin' ? 'focus' : 'blur',
      targetInst,
      nativeEvent,
      nativeEventTarget
    );
    
    if (event) {
      const reactName = domEventName === 'focusin' ? 'onFocus' : 'onBlur';
      
      dispatchQueue.push({
        event,
        listeners: accumulateSinglePhaseListeners(
          targetInst,
          reactName,
          domEventName
        )
      });
    }
  }
}

6.3 鼠标移入移出

typescript
// MouseEnter/MouseLeave
function extractMouseEnterLeaveEvent(
  dispatchQueue: DispatchQueue,
  domEventName: string,
  targetInst: Fiber | null,
  nativeEvent: MouseEvent,
  nativeEventTarget: EventTarget | null
) {
  // mouseenter/mouseleave不冒泡
  // React通过mouseover/mouseout模拟
  
  const isOverEvent = domEventName === 'mouseover';
  const isOutEvent = domEventName === 'mouseout';
  
  if (!isOverEvent && !isOutEvent) {
    return;
  }
  
  const from = isOutEvent ? nativeEvent.relatedTarget : nativeEventTarget;
  const to = isOverEvent ? nativeEvent.relatedTarget : nativeEventTarget;
  
  // 检查是否真的进入/离开
  if (from === to) {
    return;
  }
  
  let leave, enter, leaveEventType, enterEventType;
  
  if (isOverEvent) {
    leave = 'onMouseLeave';
    enter = 'onMouseEnter';
    leaveEventType = 'mouseleave';
    enterEventType = 'mouseenter';
  } else {
    leave = 'onPointerLeave';
    enter = 'onPointerEnter';
    leaveEventType = 'pointerleave';
    enterEventType = 'pointerenter';
  }
  
  const fromNode = from == null ? window : from;
  const toNode = to == null ? window : to;
  
  const fromInst = getClosestInstanceFromNode(fromNode);
  const toInst = getClosestInstanceFromNode(toNode);
  
  // 创建leave事件
  const leaveEvent = SyntheticMouseEvent.getPooled(
    leaveEventType,
    fromInst,
    nativeEvent,
    nativeEventTarget
  );
  leaveEvent.type = leaveEventType;
  leaveEvent.target = fromNode;
  leaveEvent.relatedTarget = toNode;
  
  // 创建enter事件
  const enterEvent = SyntheticMouseEvent.getPooled(
    enterEventType,
    toInst,
    nativeEvent,
    nativeEventTarget
  );
  enterEvent.type = enterEventType;
  enterEvent.target = toNode;
  enterEvent.relatedTarget = fromNode;
  
  dispatchQueue.push(
    { event: leaveEvent, listeners: /* ... */ },
    { event: enterEvent, listeners: /* ... */ }
  );
}

7. 性能优化

7.1 批量更新

typescript
// 事件触发的批量更新
function batchedEventUpdates<A, R>(fn: (a: A) => R, a: A): R {
  const prevExecutionContext = executionContext;
  executionContext |= EventContext;
  
  try {
    return fn(a);
  } finally {
    executionContext = prevExecutionContext;
    
    if (executionContext === NoContext) {
      // 刷新同步队列
      flushSyncCallbackQueue();
    }
  }
}

// 示例
function handleMultipleUpdates() {
  // 这些更新会被批量处理
  setCount(c => c + 1);
  setName('John');
  setAge(25);
  // 只触发一次重新渲染
}

7.2 事件优先级

typescript
// 不同事件不同优先级
function runWithPriority<T>(priority: EventPriority, fn: () => T): T {
  const previousPriority = currentUpdatePriority;
  currentUpdatePriority = priority;
  
  try {
    return fn();
  } finally {
    currentUpdatePriority = previousPriority;
  }
}

// 离散事件: 同步优先级
function dispatchDiscreteEvent(
  domEventName: string,
  eventSystemFlags: EventSystemFlags,
  container: EventTarget,
  nativeEvent: Event
) {
  const previousPriority = getCurrentUpdatePriority();
  
  try {
    setCurrentUpdatePriority(DiscreteEventPriority);
    dispatchEvent(domEventName, eventSystemFlags, container, nativeEvent);
  } finally {
    setCurrentUpdatePriority(previousPriority);
  }
}

8. 面试高频问题

typescript
const syntheticEventInterviewQA = {
  Q1: {
    question: 'React合成事件和原生事件的区别?',
    answer: [
      '1. 命名: 驼峰 vs 小写',
      '2. 处理函数: 函数引用 vs 字符串',
      '3. 阻止默认: preventDefault() vs return false',
      '4. 事件委托: 委托到根 vs 绑定到元素',
      '5. 跨浏览器: React统一处理兼容性',
      '6. 对象池: 17前复用对象'
    ]
  },
  
  Q2: {
    question: 'React事件委托原理?',
    answer: `
      React 17+:
      1. 所有事件注册到root容器
      2. 原生事件冒泡到root
      3. React根据target找到对应Fiber
      4. 从target向上收集所有监听器
      5. 按顺序执行监听器
      
      好处:
      - 减少内存占用
      - 动态绑定/解绑
      - 统一管理
    `
  },
  
  Q3: {
    question: 'React 17事件系统的变化?',
    answer: [
      '1. 事件委托从document改为root容器',
      '2. 废弃事件池,不再需要persist',
      '3. 捕获阶段优先级更高',
      '4. onScroll不再冒泡',
      '5. onFocus/onBlur使用原生事件',
      '6. 更好的与第三方库集成'
    ]
  },
  
  Q4: {
    question: '如何阻止事件冒泡?',
    answer: `
      e.stopPropagation() - 阻止React合成事件冒泡
      e.nativeEvent.stopImmediatePropagation() - 阻止原生事件
      
      注意:
      - stopPropagation只阻止React事件冒泡
      - 不影响原生事件监听器
    `
  },
  
  Q5: {
    question: '为什么合成事件不能异步访问(17前)?',
    answer: `
      事件池机制:
      1. 事件对象复用节省内存
      2. 事件处理完后清空属性
      3. 异步访问时已被回收
      
      解决:
      - 使用e.persist()保持
      - 或提前保存需要的值
      
      React 17+已废弃事件池
    `
  },
  
  Q6: {
    question: 'onChange事件如何工作?',
    answer: [
      '1. 监听多个原生事件(input, change, blur等)',
      '2. 根据元素类型选择处理逻辑',
      '3. input/textarea: 监听input事件',
      '4. checkbox/radio: 监听change事件',
      '5. select: 监听change事件',
      '6. 统一触发onChange'
    ]
  }
};

9. 最佳实践

typescript
const eventBestPractices = {
  事件绑定: {
    推荐: `
      // 类组件: 构造器绑定或箭头函数
      class MyComponent extends React.Component {
        constructor(props) {
          super(props);
          this.handleClick = this.handleClick.bind(this);
        }
        
        handleClick() {
          // ...
        }
      }
      
      // 或使用类字段
      handleClick = () => {
        // ...
      }
      
      // 函数组件: useCallback
      const handleClick = useCallback(() => {
        // ...
      }, []);
    `,
    
    避免: `
      // ❌ 每次渲染创建新函数
      <button onClick={() => handleClick()} />
      
      // ❌ bind每次创建新函数
      <button onClick={this.handleClick.bind(this)} />
    `
  },
  
  事件传参: {
    方法1: '箭头函数',
    code1: `<button onClick={() => handleClick(id)} />`,
    
    方法2: 'bind',
    code2: `<button onClick={handleClick.bind(null, id)} />`,
    
    方法3: 'data属性',
    code3: `
      <button data-id={id} onClick={handleClick} />
      
      function handleClick(e) {
        const id = e.currentTarget.dataset.id;
      }
    `
  },
  
  性能优化: [
    '使用事件委托',
    '避免内联函数',
    '使用useCallback/React.memo',
    '合理使用preventDefault',
    '大列表使用虚拟滚动'
  ]
};

10. 总结

React合成事件系统的核心要点:

  1. 跨浏览器: 统一的事件API
  2. 事件委托: 委托到root容器
  3. 事件插件: 可扩展的插件系统
  4. 优先级: 不同事件不同优先级
  5. 批量更新: 自动批处理
  6. 特殊处理: onChange、Focus等特殊事件
  7. React 17变化: 废弃事件池,改进委托
  8. 性能优化: 事件复用,减少监听器

理解合成事件是掌握React事件处理的基础。