React 基础事件插件

覆盖绝大多数日常交互事件:鼠标、键盘、触屏、指针、滚轮、剪贴、CSS 动画 / 过渡、表单 submit、弹窗 toggle、焦点等常规事件全部由该插件处理,负责事件的委托监听、合成事件创建、回调收集与调度,支撑整套合成事件体系

事件注册:registerEvents

应用初始化阶段一次性完成全部基础一对一事件的元数据注册,构建三层核心数据

  • topLevelEventsToReactNames:原生事件 ↔ JSX 事件名映射表
  • allNativeEvents:全局需要在根容器绑定的所有原生事件集合
  • 冒泡 / 捕获双阶段事件配置,供插件提取回调时区分 onXxx / onXxxCapture
javascript 复制代码
// 所有由 SimpleEventPlugin 接管的原生事件
const simpleEventPluginEvents = [
  'abort',
  'auxClick',
  'beforeToggle',
  'cancel',
  'canPlay',
  'canPlayThrough',
  'click',
  'close',
  'contextMenu',
  'copy',
  'cut',
  'drag',
  'dragEnd',
  'dragEnter',
  'dragExit',
  'dragLeave',
  'dragOver',
  'dragStart',
  'drop',
  'durationChange',
  'emptied',
  'encrypted',
  'ended',
  'error',
  'fullscreenChange',
  'fullscreenError',
  'gotPointerCapture',
  'input',
  'invalid',
  'keyDown',
  'keyPress',
  'keyUp',
  'load',
  'loadedData',
  'loadedMetadata',
  'loadStart',
  'lostPointerCapture',
  'mouseDown',
  'mouseMove',
  'mouseOut',
  'mouseOver',
  'mouseUp',
  'paste',
  'pause',
  'play',
  'playing',
  'pointerCancel',
  'pointerDown',
  'pointerMove',
  'pointerOut',
  'pointerOver',
  'pointerUp',
  'progress',
  'rateChange',
  'reset',
  'resize',
  'seeked',
  'seeking',
  'stalled',
  'submit',
  'suspend',
  'timeUpdate',
  'touchCancel',
  'touchEnd',
  'touchStart',
  'volumeChange',
  'scroll',
  'toggle',
  'touchMove',
  'waiting',
  'wheel',
];

function registerSimpleEvent(domEventName: DOMEventName, reactName: string) {
  topLevelEventsToReactNames.set(domEventName, reactName);
  // 底层注册工具,同时注册冒泡事件(onXxx)和捕获事件(onXxxCapture),并把原生事件存入全局 allNativeEvents 集合(后续根容器批量绑定事件的数据源)
  registerTwoPhaseEvent(reactName, [domEventName]);
}

export function registerSimpleEvents() {
  for (let i = 0; i < simpleEventPluginEvents.length; i++) {
    const eventName = simpleEventPluginEvents[i];
    const domEventName = eventName.toLowerCase;
    const capitalizedEvent = eventName[0].toUpperCase() + eventName.slice(1);
    registerSimpleEvent(domEventName, 'on' + capitalizedEvent);
  }
  registerSimpleEvent(ANIMATION_END, 'onAnimationEnd');
  registerSimpleEvent(ANIMATION_ITERATION, 'onAnimationIteration');
  registerSimpleEvent(ANIMATION_START, 'onAnimationStart');
  registerSimpleEvent('dblclick', 'onDoubleClick');
  registerSimpleEvent('focusin', 'onFocus');
  registerSimpleEvent('focusout', 'onBlur');

  registerSimpleEvent(TRANSITION_RUN, 'onTransitionRun');
  registerSimpleEvent(TRANSITION_START, 'onTransitionStart');
  registerSimpleEvent(TRANSITION_CANCEL, 'onTransitionCancel');
  registerSimpleEvent(TRANSITION_END, 'onTransitionEnd');
}

作用

  • 批量遍历 simpleEventPluginEvents 数组,自动注册标准事件
    • 取出数组内驼峰事件标识
    • 转为浏览器原生小写事件名(DOM addEventListener 标准名称全小写)
    • 首字母大写,拼接前缀 on 得到 JSX 事件名
    • 调用 registerSimpleEvent 完成映射与双阶段注册:建立原生事件 → React 事件名映射,存入全局 Map,事件触发时插件可反向查找对应 JSX 事件属性,用于遍历 Fiber 收集回调;注册冒泡 + 捕获双阶段事件
  • 动画/过渡特殊事件手动注册(常量映射)
  • 命名不匹配的特殊兼容事件手动注册
    • dblclickonDoubleClick:原生是缩写 dblReact 使用完整 DoubleClick
    • focusinonFocusfocusoutonBlur:浏览器原生 focus/focusout 不冒泡,React 改用冒泡的 focusin/focusout 代理,对外统一暴露 onFocus/onBlur,抹平浏览器冒泡差异

事件提取:extractEvents

  • 事件类型路由,匹配对应专属合成事件子类 :通过 switch 分支按原生事件分类,自动选择封装了对应原生字段的合成事件构造器:键盘 / 鼠标 / 触摸 / 动画 / 拖拽 / 剪贴 / 指针各有独立子类,上层业务访问 e.offsetX/e.key/e.touches 时直接可读,抹平浏览器原生字段差异
  • 区分两套 DOM 绑定模型,两套回调收集逻辑
    • 普通 JSX 渲染 DOM:遍历 Fiber 树收集组件内 onXxx
    • createEventHandle 外部非托管 DOM:读取手动绑定的回调列表
  • 统一生成合成事件并推入全局调度队列 :产出标准化 {event, listeners} 结构存入 dispatchQueue;后续 processDispatchQueue 统一遍历队列执行回调,统一管理事件池、stopPropagation 逻辑
javascript 复制代码
function extractEvents(
  dispatchQueue,
  domEventName,
  targetInst,
  nativeEvent,
  nativeEventTarget,
  eventSystemFlags,
  targetContainer,
) {
  const reactName = topLevelEventsToReactNames.get(domEventName);
  if (reactName === undefined) {
    return;
  }
  let SyntheticEventCtor = SyntheticEvent;
  let reactEventType = domEventName;
  switch (domEventName) {
    case 'keypress':
      if (getEventCharCode(nativeEvent) === 0) {
        return;
      }
    case 'keydown':
    case 'keyup':
      SyntheticEventCtor = SyntheticKeyboardEvent;
      break;
    case 'focusin':
      reactEventType = 'focus';
      SyntheticEventCtor = SyntheticFocusEvent;
      break;
    case 'focusout':
      reactEventType = 'blur';
      SyntheticEventCtor = SyntheticFocusEvent;
      break;
    case 'beforeblur':
    case 'afterblur':
      SyntheticEventCtor = SyntheticFocusEvent;
      break;
    case 'click':
      if (nativeEvent.button === 2) {
        return;
      }
    case 'auxclick':
    case 'dblclick':
    case 'mousedown':
    case 'mousemove':
    case 'mouseup':
    case 'mouseout':
    case 'mouseover':
    case 'contextmenu':
      SyntheticEventCtor = SyntheticMouseEvent;
      break;
    case 'drag':
    case 'dragend':
    case 'dragenter':
    case 'dragexit':
    case 'dragleave':
    case 'dragover':
    case 'dragstart':
    case 'drop':
      SyntheticEventCtor = SyntheticDragEvent;
      break;
    case 'touchcancel':
    case 'touchend':
    case 'touchmove':
    case 'touchstart':
      SyntheticEventCtor = SyntheticTouchEvent;
      break;
    case ANIMATION_END:
    case ANIMATION_ITERATION:
    case ANIMATION_START:
      SyntheticEventCtor = SyntheticAnimationEvent;
      break;
    case TRANSITION_END:
      SyntheticEventCtor = SyntheticTransitionEvent;
      break;
    case 'scroll':
    case 'scrollend':
      SyntheticEventCtor = SyntheticUIEvent;
      break;
    case 'wheel':
      SyntheticEventCtor = SyntheticWheelEvent;
      break;
    case 'copy':
    case 'cut':
    case 'paste':
      SyntheticEventCtor = SyntheticClipboardEvent;
      break;
    case 'gotpointercapture':
    case 'lostpointercapture':
    case 'pointercancel':
    case 'pointerdown':
    case 'pointermove':
    case 'pointerout':
    case 'pointerover':
    case 'pointerup':
      SyntheticEventCtor = SyntheticPointerEvent;
      break;
    case 'submit':
      SyntheticEventCtor = SyntheticSubmitEvent;
      break;
    case 'toggle':
    case 'beforetoggle':
      SyntheticEventCtor = SyntheticToggleEvent;
      break;
    default:
      break;
  }
  // true 收集 onXxxCapture;false 收集 onXxx
  const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
  if (
    enableCreateEventHandleAPI &&
    eventSystemFlags & IS_EVENT_HANDLE_NON_MANAGED_NODE
  ) {
    const listeners = accumulateEventHandleNonManagedNodeListeners(
      reactEventType,
      targetContainer,
      inCapturePhase,
    );
    if (listeners.length > 0) {
      const event = new SyntheticEventCtor(
        reactName,
        reactEventType,
        null,
        nativeEvent,
        nativeEventTarget,
      );
      dispatchQueue.push({event, listeners});
    }
  } else {
    const accumulateTargetOnly =
      !inCapturePhase &&
      (domEventName === 'scroll' || domEventName === 'scrollend');

    const listeners = accumulateSinglePhaseListeners(
      targetInst,
      reactName,
      nativeEvent.type,
      inCapturePhase,
      // true 仅取当前节点,不向上遍历父节点
      accumulateTargetOnly,
      nativeEvent,
    );
    if (listeners.length > 0) {
      const event = new SyntheticEventCtor(
        reactName,
        reactEventType,
        null,
        nativeEvent,
        nativeEventTarget,
      );
      dispatchQueue.push({event, listeners});
    }
  }
}

作用

  • 从全局映射表 topLevelEventsToReactNames 取出 JSX 事件名,匹配 React 事件名,无匹配直接短路退出
  • 初始化合成事件基础配置
    • SyntheticEventCtor:合成事件构造函数,默认基类 SyntheticEvent
    • reactEventType:对外暴露的事件类型标识,大部分和原生同名
  • 按原生事件匹配对应合成事件子类
    • 键盘事件 keypress /keydown/keyup
      • keypress 字符码为 0 时直接丢弃事件(功能键无输入字符,不需要触发 keypress 回调)
      • 键盘事件统一使用 SyntheticKeyboardEvent,封装 key/charCode 等键盘专属字段
    • 焦点事件 focusin/focusout/beforeblur/afterblur
      • 修改 reactEventType 让业务拿到的合成事件 typefocus/blur,屏蔽底层代理事件
      • 原生 focus 不冒泡,React 根容器监听冒泡的 focusin/focusout,对外统一暴露 onFocus/onBlur ,使用专属合成事件 SyntheticFocusEvent
    • 鼠标 / 右键 / 双击事件 click/auxclick/dblclick/mousedown
      • nativeEvent.button === 2 右键点击直接拦截,不触发 click 回调(右键走 contextmenu
      • 全部鼠标类事件使用 SyntheticMouseEvent,封装 offsetX/clientX/button 等鼠标坐标信息
    • 拖拽 drag/drop 系列:拖拽专属合成事件 SyntheticDragEvent,封装 dataTransfer 拖拽数据
    • 触摸 touch 系列:touch 专属合成事件 SyntheticTouchEvent,封装 touches/targetTouches/changedTouches 触点数组
    • 动画 / 过渡事件:动画专属合成事件 SyntheticAnimationEvent 和过渡专属合成事件 SyntheticTransitionEvent,封装动画名称、持续时间等字段
    • 滚动、滚轮、剪贴、指针、表单、toggle 等分支,分别匹配对应专属合成事件构造器
      • scroll/scrollendSyntheticUIEvent
      • wheelSyntheticWheelEvent
      • copy/cut/pasteSyntheticClipboardEvent
      • pointer 系列 → SyntheticPointerEvent
      • submitSyntheticSubmitEvent
      • toggle/beforetoggleSyntheticToggleEvent
    • 无匹配事件保留默认基类 SyntheticEvent
  • 判断当前是否为捕获阶段
  • createEventHandle 非托管外部 DOM:事件来自非 React 渲染的外部 DOM(第三方 DOM、动态创建脱离 Fiber 树的节点)
    • 调用 accumulateEventHandleNonManagedNodeListeners,不从 Fiber 树收集,而是读取 createEventHandle 手动绑定的回调列表
    • 存在回调则实例化对应合成事件,推入调度队列
  • 标准 React 托管 DOM(绝大多数业务场景)
    • 滚动事件特殊标记 accumulateTargetOnly:冒泡 scroll/scrollend 开启 accumulateTargetOnly,禁止向上遍历 Fiber,高频滚动事件大幅减少遍历开销
    • 调用 accumulateSinglePhaseListeners 收集单阶段回调:从 targetInst 向上遍历 Fiber 树,匹配当前阶段(捕获 / 冒泡)的 reactName(onXxx),收集所有事件回调函数,返回回调数组
    • 存在回调则生成合成事件入队:创建对应子类合成事件,封装全部上下文,将 {event, listeners} 推入共享 dispatchQueue,等待 processDispatchQueue 按顺序执行

辅助函数

注册两阶段事件:registerTwoPhaseEvent

统一注册成对的冒泡 / 捕获双阶段事件 :先注册普通冒泡事件,再自动生成并注册同名捕获事件,一次性完成两套事件注册,统一传入 dependencies 保证两套事件共用同一原生监听,不会重复绑定原生事件,节约性能

javascript 复制代码
export function registerTwoPhaseEvent(
  registrationName,
  dependencies,
) {
  registerDirectEvent(registrationName, dependencies);
  registerDirectEvent(registrationName + 'Capture', dependencies);
}

单阶段事件注册:registerDirectEvent

  • 持久化事件映射关系 :记录 JSX 声明事件和底层要监听的原生事件的绑定关系,供事件收集、分发流程读取
  • 收集全部需要委托的原生事件:统一汇总所有原生事件到全局集合,自动去重,无重复绑定,避免重复给根节点绑定相同原生监听,节约浏览器性能
javascript 复制代码
export function registerDirectEvent(
  registrationName,
  dependencies,
) {
  // JSX 事件名 → 对应原生 DOM 事件数组的映射表
  registrationNameDependencies[registrationName] = dependencies;
  for (let i = 0; i < dependencies.length; i++) {
    allNativeEvents.add(dependencies[i]);
  }
}

合成事件通用工厂构造器:createSyntheticEvent

接收一份 Interface 属性规范配置,动态生成一套独立的合成事件构造函数 SyntheticBaseEvent

  • 实例自动挂载内部私有上下文字段(原生 eventFiber、标准化 target、事件类型等全部元数据),插件、回调、回放逻辑可完整读取事件触发时的环境。
    • _reactName:原始 JSX 绑定名(onClick),内部插件系统使用
    • _targetInst:事件目标 Fiber,用于内部遍历、事件回放
    • type:业务读取 e.type 拿到的标准化事件类型(抹平 focusin → focus
    • nativeEvent:持有原生事件引用,所有属性读取底层依赖它
    • target:标准化事件触发 DOM
    • currentTarget:遍历 Fiber 执行回调时动态赋值,代表当前正在执行回调的 DOM
  • 遍历 Interface 配置批量生成实例标准化属性
    • 过滤原型链继承属性,只处理自身定义字段
    • 如果是函数:执行函数,传入原生 event,将返回值赋值给实例
    • 如果是数字 0:直接读取 nativeEvent[propName] 赋值给实例
  • 初始化 isDefaultPrevented 标记(兼容旧浏览器)
    • 现代标准:nativeEvent.defaultPrevented
    • IE 旧浏览器:无该字段,使用 returnValue === false 判断是否阻止默认行为;提前绑定固定返回函数,后续调用 e.isDefaultPrevented() 直接返回布尔值,无需重复判断
  • 初始化冒泡停止标记:默认未阻止冒泡,绑定永远返回 false 的函数;调用 stopPropagation() 后替换为返回 true 的函数
  • 原型统一注入跨浏览器兼容的 preventDefault / stopPropagation / persist 等标准方法,内置状态标记体系,支撑事件队列中断逻辑
    • preventDefault 阻止默认行为(全浏览器兼容)
      • 标记实例 defaultPrevented 状态
      • 存在原生事件时,标准浏览器调用 preventDefault()
      • IE 兼容:赋值 returnValue = false
      • 重写 isDefaultPrevented 为永久返回 true 的函数,后续调用直接命中
    • stopPropagation 停止冒泡(全浏览器兼容)
      • 标准:event.stopPropagation()
      • IE 旧版:cancelBubble = true
      • 标记 isPropagationStopped 永久返回 true,后续遍历队列时截断同阶段剩余回调
    • persist /isPersistent 事件持久化(事件池配套):实例可存入事件池复用,减少高频 UI 事件 GC 开销
      • 基础工厂默认空实现,子类 / 事件池逻辑会重写 persist
      • isPersistent 默认返回 true,标记该事件是否脱离事件池、不会被自动回收
javascript 复制代码
function createSyntheticEvent(Interface) {
  function SyntheticBaseEvent(
    reactName,
    reactEventType,
    targetInst,
    nativeEvent,
    nativeEventTarget,
  ) {
    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];
      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;
  }
  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;
      }
      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;
      }

      this.isPropagationStopped = functionThatReturnsTrue;
    },

    persist: function () {
    },
    isPersistent: functionThatReturnsTrue,
  });
  return SyntheticBaseEvent;
}

function functionThatReturnsTrue() {
  return true;
}

function functionThatReturnsFalse() {
  return false;
}

设计意义

  • 一次性快照缓存属性,简化运行时访问 :实例化时一次性计算所有属性存入实例,而非使用 getter 动态读取原生 event。事件池回收后原生 event 置空,避免内存泄漏,属性值仍保留,兼容 persist() 持久化场景;牺牲少量初始化开销,换取回调内稳定、无依赖的属性访问
  • 隔离原生 DOM Event,避免污染原生对象 :业务操作的是封装后的合成事件,不会直接修改浏览器原生 Event,规避浏览器内部状态异常
  • 延迟代理原生属性,面向高频交互性能优化 :仅定义映射规则,实例访问时才读取原生 event,初始化不复制大量字段,不提前拷贝坐标、按键数据,减少实例初始化开销,不占用主线程预计算,优化 scroll/mousemove/touchmove 等高频事件性能

基础合成事件构造器:SyntheticEvent

所有合成事件(鼠标 / 键盘 / 触摸 / 动画等)的公共父类,集中定义所有基础事件通用字段

  • 代理原生 event.eventPhase,标识事件阶段(捕获 / 目标 / 冒泡),同步到合成事件
  • 代理原生 event.bubbles,标记事件是否会冒泡
  • 代理原生 event.cancelable,标记是否可以调用 preventDefault() 阻止默认行为
  • 浏览器兼容补丁:部分老旧浏览器原生 eventtimeStamp 或值为 0;取不到时降级使用当前时间戳 Date.now(),保证 e.timeStamp 永远有合法数值
  • 代理原生 event.defaultPrevented,标记是否已经阻止默认行为
  • 代理原生 event.isTrusted,区分是用户真实交互还是脚本模拟触发的事件
javascript 复制代码
const EventInterface = {
  eventPhase: 0, 
  bubbles: 0,
  cancelable: 0,
  timeStamp: function (event) {
    return event.timeStamp || Date.now();
  },
  defaultPrevented: 0,
  isTrusted: 0,
}
export const SyntheticEvent = createSyntheticEvent(EventInterface);

UI 层合成事件构造器:SyntheticUIEvent

继承基础 SyntheticEvent,是所有 UI 派生事件(鼠标、滚轮、滚动、指针、拖拽)的公共父类

  • view:对应原生 UIEvent.view,指向触发事件的窗口 window 对象;鼠标、滚动、指针、键盘事件都依赖该属性
  • detail原生 UI 事件细节数值:
    • click/mousedown:点击次数(单击 = 1,双击 = 2
    • scroll/resize:滚动 / 尺寸变更相关数值;不同 UI 事件有不同含义,统一由合成事件代理透出
javascript 复制代码
const UIEventInterface = {
  ...EventInterface,
  view: 0,
  detail: 0,
};
export const SyntheticUIEvent = createSyntheticEvent(UIEventInterface);

鼠标合成事件构造器:SyntheticMouseEvent

鼠标系列事件 click/mousedown/mousemove/mouseover/contextmenu/dblclick 的公共父类。继承 UI 事件属性 + 基础事件属性,统一鼠标事件标准化字段 ,整合坐标、修饰键、按键、关联元素、鼠标偏移全套鼠标专属 API

  • screenX/screenY:鼠标相对于屏幕左上角坐标
  • clientX/clientY:鼠标相对于视口(浏览器窗口)坐标
  • pageX/pageY:鼠标相对于整个文档页面(包含滚动)坐标;统一对外标准化鼠标点位
  • ctrlKey/shiftKey/altKey/metaKey:修饰键布尔标记,代理原生鼠标事件的按键状态Ctrl/Shift/Alt/Win/Command
  • getModifierState:现代浏览器可正常判断 CapsLock/NumLock/ScrollLock,旧浏览器降级支持 Shift/Control/Alt/Meta,统一判断锁定键状态,抹平浏览器接口差异
  • button:本次触发的鼠标按键(0 左键、1 中键、2 右键)
  • buttons:当前所有按下的按键掩码(多键同时按住)
  • relatedTarget:原生 mouseover/mouseout 标准字段 relatedTarget,旧版 IE 不支持,使用 fromElement / toElement 降级兼容
    • mouseoverrelatedTarget = 鼠标离开的元素 = fromElement
    • mouseoutrelatedTarget = 鼠标进入的元素 = toElement
  • movementX /movementY:原生属性代表两次 mousemove 之间鼠标相对偏移 ,多用于拖拽、画布绘图
    • 浏览器原生支持 → 直接返回原生值
    • 不支持 → 调用 updateMouseMovementPolyfillState 计算偏移,返回全局缓存 lastMovementX/Y:模块全局缓存上一次鼠标事件,仅在 mousemove 时计算偏移
      • 判断当前事件是否为全新 mousemove,避免重复计算
      • 存在上一次鼠标事件,且当前是 mousemove:用两次 screen 坐标差值模拟 movementX/Y
      • mousemove 或无上次事件:偏移重置为 0
      • 更新全局缓存,保存本次原生事件作为下一次计算基准
javascript 复制代码
const MouseEventInterface = {
  ...UIEventInterface,
  screenX: 0,
  screenY: 0,
  clientX: 0,
  clientY: 0,
  pageX: 0,
  pageY: 0,
  ctrlKey: 0,
  shiftKey: 0,
  altKey: 0,
  metaKey: 0,
  getModifierState: getEventModifierState,
  button: 0,
  buttons: 0,
  relatedTarget: function (event) {
    if (event.relatedTarget === undefined)
      return event.fromElement === event.srcElement
        ? event.toElement
        : event.fromElement;

    return event.relatedTarget;
  },
  movementX: function (event) {
    if ('movementX' in event) {
      return event.movementX;
    }
    updateMouseMovementPolyfillState(event);
    return lastMovementX;
  },
  movementY: function (event) {
    if ('movementY' in event) {
      return event.movementY;
    }
    return lastMovementY;
  },
};

function updateMouseMovementPolyfillState(event) {
  if (event !== lastMouseEvent) {
    if (lastMouseEvent && event.type === 'mousemove') {
      lastMovementX = event.screenX - lastMouseEvent.screenX;
      lastMovementY = event.screenY - lastMouseEvent.screenY;
    } else {
      lastMovementX = 0;
      lastMovementY = 0;
    }
    lastMouseEvent = event;
  }
}

export const SyntheticMouseEvent = createSyntheticEvent(MouseEventInterface);

拖拽合成事件构造器:SyntheticDragEvent

拖拽系列事件(drag/dragstart/dragenter/dragover/dragleave/dragend/drop)的公共父类。继承 UI 事件属性 + 基础事件属性 + 鼠标事件属性,统一代理原生拖拽数据对象,业务可通过拖拽事件独有原生对象 e.dataTransfer 读写拖拽内容(承载拖拽文件、文本、自定义拖拽数据)

  • e.dataTransfer.setData() / getData() 读写拖拽文本
  • e.dataTransfer.files 获取拖拽文件
  • 设置拖拽效果 effectAllowed/dropEffect
javascript 复制代码
const DragEventInterface = {
  ...MouseEventInterface,
  dataTransfer: 0,
};
export const SyntheticDragEvent = createSyntheticEvent(DragEventInterface);

焦点系列合成事件构造器:FocusEventInterface

焦点系列事件(onFocus/onBlur/onBeforeBlur/onAfterBlur)的公共父类。继承 UI 事件属性 + 基础事件属性,底层监听冒泡的 focusin/focusout,抹平原生 focus/focus 不冒泡的底层缺陷,对外暴露 onFocus/onBlur,全部使用该合成事件;同时承载 beforeblur/afterblur 事件。实例创建时直接读取原生 FocusEvent.relatedTarget 赋值给合成事件

  • focusinrelatedTarget = 失去焦点的旧元素
  • focusoutrelatedTarget = 将要获得焦点的新元素
javascript 复制代码
const FocusEventInterface = {
  ...UIEventInterface,
  relatedTarget: 0,
};
export const SyntheticFocusEvent = createSyntheticEvent(FocusEventInterface);

动画合成事件构造器:SyntheticAnimationEvent

动画系列事件(onAnimationStart / onAnimationIteration / onAnimationEnd)的公共父类。继承 UI 事件属性 + 基础事件属性 + 鼠标事件属性

  • 代理原生 event.animationName:原生动画名称,对应 CSS animation-name,区分是哪一段动画触发
  • 代理原生 event.elapsedTime:动画已执行时长(秒),不含延迟时间
  • 代理原生 event.pseudoElement:触发动画的伪元素,例如 ::before / ::after,格式 ::selector;无伪元素时值为空字符串
javascript 复制代码
const AnimationEventInterface = {
  ...EventInterface,
  animationName: 0,
  elapsedTime: 0,
  pseudoElement: 0,
};
export const SyntheticAnimationEvent = createSyntheticEvent(
  AnimationEventInterface,
);

剪贴板合成事件构造器:SyntheticClipboardEvent

剪贴板系列事件(onCopy/onCut/onPaste)的公共父类。继承基础事件属性,封装剪贴板数据对象与全套基础事件能力

  • clipboardData:执行兼容逻辑,拿到标准剪贴板对象存入实例
javascript 复制代码
const ClipboardEventInterface = {
  ...EventInterface,
  clipboardData: function (event) {
    return 'clipboardData' in event
      ? event.clipboardData
      : window.clipboardData;
  },
};
export const SyntheticClipboardEvent = createSyntheticEvent(
  ClipboardEventInterface,
);

输入法编辑合成事件构造器:SyntheticCompositionEvent

输入法编辑系列事件(onCompositionStart / onCompositionUpdate / onCompositionEnd)的公共父类。继承基础事件属性,封装输入法候选文本 data适配受控表单输入场景 ,配合 ChangeEventPlugin 抹平中文输入法输入时 onChange 提前触发的缺陷,依靠 CompositionEvent 区分拼音候选与最终文字

  • data:实例创建时直接读取原生 CompositionEvent.data 赋值给合成事件实例,业务通过 e.data 获取输入法编辑内容,用于处理中文输入受控组件、输入过滤等场景
    • compositionstartdata 为空
    • compositionupdate:当前输入法候选字符
    • compositionend:最终确认输入的完整文本
javascript 复制代码
const CompositionEventInterface = {
  ...EventInterface,
  data: 0,
};
export const SyntheticCompositionEvent = createSyntheticEvent(
  CompositionEventInterface,
);

键盘合成事件构造器:SyntheticKeyboardEvent

键盘系列事件(keydown / keyup / keypress)的公共父类。继承 UI 事件属性 + 基础事件属性,封装按键文本、物理编码、修饰键、长按状态全套键盘信息

  • 收敛键盘全系列浏览器兼容补丁key 标准化、charCode/keyCode/which 分事件类型适配、修饰键查询全部收拢在一处配置,业务无需写浏览器分支

  • 对齐 W3C KeyboardEvent 标准 :完整覆盖现代标准字段 key/code/location,同时兼容老旧 API keyCode/charCode/which,新旧业务代码均可平滑运行

  • 区分三类键盘事件字段行为 :针对 keydown / keyup / keypress 差异化处理编码字段,符合原生事件语义,不会出现无效编码值

  • getEventKey:统一标准化按键名称(Enter/Space/a/ArrowDown),抹平 IE、旧 Chrome 对 event.key 输出不一致的浏览器差异

  • code:物理按键编码(KeyA/Numpad1),不受键盘布局影响

  • location:区分主键盘 / 数字小键盘 / 左右修饰键;

  • ctrlKey/shiftKey/altKey/metaKey:修饰键按下状态

  • repeat:长按自动重复触发标记

  • locale:键盘语言区域

  • getModifierState:支持查询 CapsLock/Shift/Control 等按键锁定 / 按下状态

  • charCode

    • keypress 事件有有效字符编码,返回过滤后的可打印字符码
    • keydown/keyup 无字符含义,统一返回 0
    • getEventCharCode 会过滤功能键(回车、方向键等)返回 0,保证只有可输入字符有编码
  • keyCode:兼容老旧浏览器依赖 keyCode 判断按键的业务代码

    • keydown/keyup:读取原生 keyCode(物理按键数字编码)
    • keypress:无物理按键编码含义,返回 0
  • which:跨浏览器兼容,旧版浏览器统一使用 event.which 获取编码,对外统一 e.which,抹平浏览器 charCode/keyCode/which 碎片化差异

    • keypress → 字符编码(同 charCode
    • keydown/keyup → 物理按键编码(同 keyCode
javascript 复制代码
const KeyboardEventInterface = {
  ...UIEventInterface,
  key: getEventKey,
  code: 0,
  location: 0,
  ctrlKey: 0,
  shiftKey: 0,
  altKey: 0,
  metaKey: 0,
  repeat: 0,
  locale: 0,
  getModifierState: getEventModifierState,
  charCode: function (event) {
    if (event.type === 'keypress') {
      return getEventCharCode(
        event,
      );
    }
    return 0;
  },
  keyCode: function (event) {
    if (event.type === 'keydown' || event.type === 'keyup') {
      return event.keyCode;
    }
    return 0;
  },
  which: function (event) {
    if (event.type === 'keypress') {
      return getEventCharCode(
        event,
      );
    }
    if (event.type === 'keydown' || event.type === 'keyup') {
      return event.keyCode;
    }
    return 0;
  },
};
export const SyntheticKeyboardEvent = createSyntheticEvent(
  KeyboardEventInterface,
);

指针合成事件构造器:SyntheticPointerEvent

指针系列事件(pointerdown/pointermove/pointerover/gotpointercapture)的公共父类。继承 UI 事件属性 + 基础事件属性 + 鼠标事件属性,统一封装鼠标、多指触控、手写笔数据(触点尺寸、压力、倾斜、设备类型、多触点 ID

  • pointerId:触点唯一 ID,区分多指触控,是多触点缓存、事件区分核心标识,为上层 queueIfContinuousEvent 多触点 Map 缓存提供唯一标识,实现多指事件互不干扰
  • width / height:触点接触屏幕的矩形宽高
  • pressure:按压压力(0~1,鼠标恒为 0.5
  • tangentialPressure:手写笔侧压
  • tiltX / tiltY:手写笔倾斜角度
  • twist:手写笔旋转角度
  • pointerType:输入设备类型,mouse / touch / pen
  • isPrimary:是否为主触点(多指时第一个按下的手指)
javascript 复制代码
const PointerEventInterface = {
  ...MouseEventInterface,
  pointerId: 0,
  width: 0,
  height: 0,
  pressure: 0,
  tangentialPressure: 0,
  tiltX: 0,
  tiltY: 0,
  twist: 0,
  pointerType: 0,
  isPrimary: 0,
};
export const SyntheticPointerEvent = createSyntheticEvent(
  PointerEventInterface,
);

表单提交合成事件构造器:SyntheticSubmitEvent

表单提交事件(onSubmit)的公共父类。继承基础事件属性,仅保留表单提交独有的 submitter 字段,完整还原原生提交事件核心能力

  • submitter:业务中可通过该字段获取触发提交的按钮元素,区分不同提交逻辑
    • 有提交按钮:e.submitter 为触发提交的 <button>/<input> DOM
    • 回车提交无单独按钮:submitternull
javascript 复制代码
const SubmitEventInterface = {
  ...EventInterface,
  submitter: 0,
};
export const SyntheticSubmitEvent = createSyntheticEvent(SubmitEventInterface);

移动端触摸合成事件构造器:SyntheticTouchEvent

表单提交事件(onTouchStart / onTouchMove / onTouchEnd / onTouchCancel)的公共父类。继承基础事件属性 + UI 事件属性,追加触摸独有的三组触点数组,完整封装多点触摸核心数组

  • 抹平移动端浏览器触摸 API 细微差异 :底层统一代理原生 TouchEventiOS/Android 浏览器触点字段行为对外标准化,业务无需区分设备

  • 支撑移动端手势交互开发 :通过 touches/changedTouches 实现单指滑动、双指缩放、长按等手势逻辑,是移动端交互基础载体,是手势、拖拽、多点绘图核心 API

  • touches:当前屏幕上所有未松开的手指触点集合

  • targetTouches:当前触发事件元素上保留的触点

  • changedTouches:本次事件发生变化的触点(按下 / 抬起 / 移动的手指)

  • altKey/metaKey/ctrlKey/shiftKey:代理原生触摸事件修饰键状态,平板外接键盘时生效,和鼠标、键盘事件字段命名完全统一

  • getModifierState:挂载修饰键查询方法,复用统一兼容逻辑,可查询 CapsLock/Shift/Control 等锁定、按下状态

javascript 复制代码
const TouchEventInterface = {
  ...UIEventInterface,
  touches: 0,
  targetTouches: 0,
  changedTouches: 0,
  altKey: 0,
  metaKey: 0,
  ctrlKey: 0,
  shiftKey: 0,
  getModifierState: getEventModifierState,
};
export const SyntheticTouchEvent = createSyntheticEvent(TouchEventInterface);

CSS 过渡合成事件构造器:SyntheticTransitionEvent

CSS 过渡系列事件(transitionstart / transitionrun / transitioncancel / transitionend)的公共父类。继承基础事件属性,封装过渡属性、运行时长、伪元素标识,配套全套基础事件能力。适配样式动画业务场景 ,用于监听弹窗、卡片、元素入场离场过渡完成,执行后续 DOM 操作、状态变更

  • propertyName:触发过渡的 CSS 属性名,例如 width / opacity / transform,区分是哪一条样式过渡触发事件
  • elapsedTime:过渡已运行时长(单位秒),不含 transition-delay 延迟时间
  • pseudoElement:触发过渡的伪元素标识,格式 ::before / ::after;普通 DOM 元素为空字符串
javascript 复制代码
const TransitionEventInterface = {
  ...EventInterface,
  propertyName: 0,
  elapsedTime: 0,
  pseudoElement: 0,
};
export const SyntheticTransitionEvent = createSyntheticEvent(
  TransitionEventInterface,
);

鼠标滚轮合成事件构造器:SyntheticWheelEvent

鼠标滚轮系列事件(wheel)的公共父类,继承 UI 事件属性 + 基础事件属性 + 鼠标事件属性,封装标准化滚轮偏移数据(deltaX/deltaYdeltaZdeltaMode

  • 抹平各浏览器滚轮数值正负、API 碎片化差异 :底层统一换算偏移方向,上层业务无需区分 Chrome/Safari/ 旧版浏览器

  • 适配滚动、画布缩放、页面缩放等高频场景 :滚轮属于高频连续事件,配合事件池复用实例,减少频繁 GC 卡顿

  • deltaX:水平滚轮偏移

    • 现代标准浏览器存在 deltaX,直接使用原生值
    • WebKit 旧浏览器无 deltaX,使用 wheelDeltaX取负号反转符号 (两套 API 滚动方向正负相反)
    • 都不存在则返回 0,避免 undefined 报错
  • deltaY:垂直滚轮偏移

    • 标准 deltaY 直接返回
    • 存在 wheelDeltaY 取负反转
    • 老旧浏览器仅提供单值 wheelDelta(垂直滚动),取负使用
    • 无任何滚轮属性返回 0 兜底
  • deltaZ:三维滚轮 Z 轴滚动偏移

  • deltaMode:偏移单位标记(像素、行、页),区分滚动步长单位

javascript 复制代码
const WheelEventInterface = {
  ...MouseEventInterface,
  deltaX(event) {
    return 'deltaX' in event
      ? event.deltaX
      : 'wheelDeltaX' in event
        ? -event.wheelDeltaX
        : 0;
  },
  deltaY(event) {
    return 'deltaY' in event
      ? event.deltaY
      : 'wheelDeltaY' in event
        ? -event.wheelDeltaY
        : 'wheelDelta' in event
          ? -event.wheelDelta
          : 0;
  },
  deltaZ: 0,
  deltaMode: 0,
};
export const SyntheticWheelEvent = createSyntheticEvent(WheelEventInterface);

折叠 / 弹出组件合成事件构造器:SyntheticToggleEvent

折叠 / 弹出组件系列事件(toggle / beforetoggle)的公共父类。继承基础事件属性,封装前后状态标识,适配折叠面板、弹窗、气泡卡片业务场景 ,依靠 newState/oldState 实现展开 / 关闭动画、状态同步、弹窗定位等逻辑用于 <details><dialog><popover> 等折叠 / 弹出组件状态切换,完整还原原生折叠弹窗事件核心能力

-oldState:切换前状态 open / closed

  • newState:切换后状态 open / closed
javascript 复制代码
const ToggleEventInterface = {
  ...EventInterface,
  newState: 0,
  oldState: 0,
};
export const SyntheticToggleEvent = createSyntheticEvent(ToggleEventInterface);

读取修饰键状态:getEventModifierState

  • 统一封装修饰键查询兼容逻辑 :一套逻辑同时兼容两种浏览器实现:标准 getModifierState API、旧版 xxxKey 布尔属性
  • 支持标准锁定键与普通修饰键 :现代浏览器可正常判断 CapsLock/NumLock,旧浏览器降级支持 Ctrl/Shift/Alt/Meta
javascript 复制代码
const modifierKeyToProp = {
  Alt: 'altKey',
  Control: 'ctrlKey',
  Meta: 'metaKey',
  Shift: 'shiftKey',
};

function modifierStateGetter(keyArg) {
  const syntheticEvent = this;
  const nativeEvent = syntheticEvent.nativeEvent;
  if (nativeEvent.getModifierState) {
    return nativeEvent.getModifierState(keyArg);
  }
  const keyProp = modifierKeyToProp[keyArg];
  return keyProp ? !!nativeEvent[keyProp] : false;
}

function getEventModifierState(nativeEvent) {
  return modifierStateGetter;
}

获取键盘事件编码:getEventCharCode

键盘事件 charCode 标准化兼容工具,抹平 Firefox/IE/Chrome/Safari 各浏览器对 charCode / keyCode 的实现差异,输出统一、规范的可打印字符编码,供给 SyntheticKeyboardEventcharCode / which 字段使用

  • 现代浏览器支持 charCodeChrome/Firefox/Safari):优先使用原生 charCodeFirefox 兼容,按下回车时 charCode 为 0,强制修正为 13
  • IE8 等老旧浏览器无 charCode 属性:直接复用 keyCode 作为字符编码,Windows 系统兼容,按住 Ctrl+Enter 时浏览器会返回换行符 10,统一修正为回车标准码 13
  • 可打印字符过滤规则
    • >=32:空格及所有可打印字符(字母、数字、符号)保留
    • 13:回车 Enter 特殊放行
    • 其余控制键(退格、方向键、F 功能键等)全部返回 0
javascript 复制代码
function getEventCharCode(nativeEvent) {
  let charCode;
  const keyCode = nativeEvent.keyCode;

  if ('charCode' in nativeEvent) {
    charCode = nativeEvent.charCode;

    if (charCode === 0 && keyCode === 13) {
      charCode = 13;
    }
  } else {
    charCode = keyCode;
  }

  if (charCode === 10) {
    charCode = 13;
  }

  if (charCode >= 32 || charCode === 13) {
    return charCode;
  }

  return 0;
}

createEventHandle 的回调收集器:accumulateEventHandleNonManagedNodeListeners

外部非托管 DOM(脱离 React 渲染树,用 createEventHandle 手动绑定),读取手动挂载的监听列表

  • 初始化空回调数组 listeners:存储匹配当前事件类型、当前阶段的所有用户回调,最终返回给上层用于生成合成事件入队
  • 获取外部非托管 DOM 节点(脱离 Fiber 树,非 JSX 渲染节点)上通过 createEventHandle 绑定的全部私有监听记录
  • 存在监听条目则遍历过滤匹配项:监听绑定的事件类型和当前触发事件一致且监听阶段和当前事件阶段匹配(捕获 / 冒泡)
  • 包装标准调度监听对象,追加到回调列表
  • 返回匹配完成的回调数组
javascript 复制代码
export function accumulateEventHandleNonManagedNodeListeners(
  reactEventType,
  currentTarget,
  inCapturePhase,
) {
  const listeners = [];

  const eventListeners = getEventHandlerListeners(currentTarget);
  if (eventListeners !== null) {
    eventListeners.forEach(entry => {
      if (entry.type === reactEventType && entry.capture === inCapturePhase) {
        listeners.push(
          createDispatchListener(null, entry.callback, currentTarget),
        );
      }
    });
  }
  return listeners;
}

事件回调收集:accumulateSinglePhaseListeners

事件系统核心回调收集函数 ,专门遍历 Fiber 树,按事件传播顺序收集当前捕获 / 冒泡阶段、当前事件类型的全部事件回调,currentTarget 随遍历层级动态绑定对应 DOM,同时兼容两套绑定体系,支持 Scope 作用域组件、beforeblur 焦点特殊拦截、仅收集目标节点不冒泡等能力,产出标准化 createDispatchListener 封装的回调数组,供给 processDispatchQueue 批量执行事件回调。内置焦点特殊拦截能力 ,原生 beforeblur 焦点劫持逻辑封装在内,支持弹窗、浮层焦点捕获场景,无需上层特殊分支

  • JSX 声明式绑定:<div onClick={fn}> 存储在 Fiber.props
  • createEventHandle 命令式绑定:DOM / Scope 节点私有缓存监听
javascript 复制代码
export function accumulateSinglePhaseListeners(
  targetFiber,
  reactName,
  nativeEventType,
  inCapturePhase,
  accumulateTargetOnly,
  nativeEvent,
) {
  const captureName = reactName !== null ? reactName + 'Capture' : null;
  const reactEventName = inCapturePhase ? captureName : reactName;
  let listeners = [];

  let instance = targetFiber;
  let lastHostComponent = null;

  while (instance !== null) {
    const {stateNode, tag} = instance;
    if (
      (tag === HostComponent ||
        tag === HostHoistable ||
        tag === HostSingleton) &&
      stateNode !== null
    ) {
      lastHostComponent = stateNode;

      if (enableCreateEventHandleAPI) {
        const eventHandlerListeners =
          getEventHandlerListeners(lastHostComponent);
        if (eventHandlerListeners !== null) {
          eventHandlerListeners.forEach(entry => {
            if (
              entry.type === nativeEventType &&
              entry.capture === inCapturePhase
            ) {
              listeners.push(
                createDispatchListener(
                  instance,
                  entry.callback,
                  (lastHostComponent: any),
                ),
              );
            }
          });
        }
      }

      if (reactEventName !== null) {
        const listener = getListener(instance, reactEventName);
        if (listener != null) {
          listeners.push(
            createDispatchListener(instance, listener, lastHostComponent),
          );
        }
      }
    } else if (
      enableCreateEventHandleAPI &&
      enableScopeAPI &&
      tag === ScopeComponent &&
      lastHostComponent !== null &&
      stateNode !== null
    ) {
      const reactScopeInstance = stateNode;
      const eventHandlerListeners =
        getEventHandlerListeners(reactScopeInstance);
      if (eventHandlerListeners !== null) {
        eventHandlerListeners.forEach(entry => {
          if (
            entry.type === nativeEventType &&
            entry.capture === inCapturePhase
          ) {
            listeners.push(
              createDispatchListener(
                instance,
                entry.callback,
                (lastHostComponent: any),
              ),
            );
          }
        });
      }
    }
    if (accumulateTargetOnly) {
      break;
    }
    if (enableCreateEventHandleAPI && nativeEvent.type === 'beforeblur') {
      const detachedInterceptFiber = nativeEvent._detachedInterceptFiber;
      if (
        detachedInterceptFiber !== null &&
        (detachedInterceptFiber === instance ||
          detachedInterceptFiber === instance.alternate)
      ) {
        listeners = [];
      }
    }
    instance = instance.return;
  }
  return listeners;
}

作用

  • 事件名区分:生成捕获 / 冒泡对应的事件 prop
  • 初始化回调数组与遍历缓存变量
    • listeners:存放最终收集到的标准化回调对象
    • instance:循环遍历指针,从目标 Fiber 向上遍历父链 return
    • lastHostComponent:缓存当前遍历到的宿主 DOM 节点(HostComponent),用于赋值 currentTarget
  • 向上遍历 Fiber 树:从事件目标 Fiber 开始,不断取 instance.return 向上遍历祖先组件,模拟 DOM 事件冒泡 / 捕获传播路径
    • 宿主 DOM 组件 HostComponent / HostHoistable / HostSingletondiv/button/input 等真实 DOM
      • 标记当前 Fiber 对应真实 DOM,存入 lastHostComponent,后续回调 currentTarget 使用该 DOM
      • 收集 createEventHandle 绑定的监听(外部 DOM 手动绑定):读取 DOM 节点上缓存的 createEventHandle 监听列表,匹配原生事件类型、匹配当前捕获 / 冒泡阶段,使用 createDispatchListener 标准化封装,存入回调数组
      • 收集 JSX 声明式 props 绑定(onClick /onClickCapture):调用 getListenerFiber.props 读取对应事件函数(onClick/onClickCapture),标准化封装加入队列
    • ScopeComponent 作用域组件(createEventHandle 作用域绑定):读取 Scope 实例缓存的手动监听,匹配事件与阶段后收集
    • 仅收集目标节点,不向上冒泡:如 enter/leave 类事件只需执行目标自身回调,无需遍历父组件,直接跳出循环,优化性能
    • beforeblur 焦点特殊拦截逻辑:如果遍历到拦截指定 Fiber,清空全部已收集回调,阻止原有焦点回调执行,实现弹窗、浮层焦点拦截功能
    • 指针上移,遍历下一层父组件:循环向上遍历祖先 Fiber,直到 instance === null(根节点之上)
  • 返回收集完成的标准化回调数组

获取 createEventHandle 监听列表:getEventHandlerListeners

读取 DOM 闭包缓存的 createEventHandle 监听列表 :专门服务命令式手动绑定 API,以内部私有唯一键读取 DOM 节点上缓存的手动监听列表,获取当前 DOM 上所有通过 createEventHandle 添加的事件监听记录

javascript 复制代码
export function getEventHandlerListeners(
  scope
) {
  return scope[internalEventHandlerListenersKey] || null;
}

创建回调单元:createDispatchListener

标准化回调单元结构,统一承载执行回调的完整元数据 :统一封装执行回调所需的全部上下文(组件 Fiber、回调函数、当前 DOM),无论 JSX 声明事件、createEventHandle 命令式绑定,都产出完全相同格式的监听对象,下游处理逻辑无需区分来源

javascript 复制代码
function createDispatchListener(
  instance,
  listener,
  currentTarget,
) {
  return {
    instance,
    listener,
    currentTarget,
  };
}

读取 JSX 声明式事件回调:getListener

输入 Fiber 实例 + 事件注册名(onClick/onMouseOverCapture 等),从 DOM 绑定的 Fiber props 中取出对应事件回调,同时做多层校验、鼠标事件特殊拦截、类型校验,最终返回合法回调或 null

javascript 复制代码
export default function getListener(
  inst,
  registrationName,
) {
  const stateNode = inst.stateNode;
  if (stateNode === null) {
    return null;
  }
  const props = getFiberCurrentPropsFromNode(stateNode);
  if (props === null) {
    return null;
  }
  const listener = props[registrationName];
  if (shouldPreventMouseEvent(registrationName, inst.type, props)) {
    return null;
  }

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

function isInteractive(tag) {
  return (
    tag === 'button' ||
    tag === 'input' ||
    tag === 'select' ||
    tag === 'textarea'
  );
}

function shouldPreventMouseEvent(
  name,
  type,
  props,
) {
  switch (name) {
    case 'onClick':
    case 'onClickCapture':
    case 'onDoubleClick':
    case 'onDoubleClickCapture':
    case 'onMouseDown':
    case 'onMouseDownCapture':
    case 'onMouseMove':
    case 'onMouseMoveCapture':
    case 'onMouseUp':
    case 'onMouseUpCapture':
    case 'onMouseEnter':
      return !!(props.disabled && isInteractive(type));
    default:
      return false;
  }
}

作用

  • 获取 Fiber 对应的宿主 DOM 节点,过滤无 DOMFiber
  • DOM 反向取出当前 Fiber 稳定 props
  • 根据事件名取出 props 上绑定的回调
  • 内置禁用元素鼠标事件拦截
    • 禁用表单元素(disabled button/input)屏蔽鼠标点击、移入移出事件
    • 特定原生标签内置行为屏蔽对应鼠标事件
  • 事件值类型严格校验,非法值抛运行时错误:如果 listener 存在且不是函数,直接抛出明确报错
  • 返回最终合法回调
相关推荐
qq_529599385 小时前
react js循环滚动
前端·javascript·react.js
索西引擎5 小时前
【React】严格模式:开发阶段质量保障的静态分析机制
前端·react.js·前端框架
小程故事多_801 天前
拆解SOP+ReAct对话Agent,告别硬编码,解锁智能对话的柔性迭代能力
前端·人工智能·react.js·前端框架
皮卡穆1 天前
React 中使用 react-org-tree 实现组织结构树
前端·react.js·前端框架
asdzx671 天前
使用 Spire.WordJS 在 React 中实现在线 Word 文档编辑
前端·react.js·word
kyriewen2 天前
AI 写的 React 组件,合并前必查的 6 个坏味道——每个都有代码对比
前端·javascript·react.js
光影少年2 天前
RN适配方案:屏幕适配、分辨率适配、刘海屏/全面屏适配
react native·react.js·掘金·金石计划
Charonmomo2 天前
react/antd - 不能正确打开标签页问题记录
前端·react.js·前端框架
用户600071819102 天前
【翻译】React Compiler 详解---memo
react.js