覆盖绝大多数日常交互事件:鼠标、键盘、触屏、指针、滚轮、剪贴、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收集回调;注册冒泡 + 捕获双阶段事件
- 动画/过渡特殊事件手动注册(常量映射)
- 命名不匹配的特殊兼容事件手动注册
dblclick→onDoubleClick:原生是缩写dbl,React使用完整DoubleClickfocusin→onFocus、focusout→onBlur:浏览器原生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:合成事件构造函数,默认基类SyntheticEventreactEventType:对外暴露的事件类型标识,大部分和原生同名
- 按原生事件匹配对应合成事件子类
- 键盘事件
keypress /keydown/keyupkeypress字符码为0时直接丢弃事件(功能键无输入字符,不需要触发keypress回调)- 键盘事件统一使用
SyntheticKeyboardEvent,封装key/charCode等键盘专属字段
- 焦点事件
focusin/focusout/beforeblur/afterblur- 修改
reactEventType让业务拿到的合成事件type为focus/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/scrollend→SyntheticUIEventwheel→SyntheticWheelEventcopy/cut/paste→SyntheticClipboardEventpointer系列 →SyntheticPointerEventsubmit→SyntheticSubmitEventtoggle/beforetoggle→SyntheticToggleEvent
- 无匹配事件保留默认基类
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
- 实例自动挂载内部私有上下文字段(原生
event、Fiber、标准化target、事件类型等全部元数据),插件、回调、回放逻辑可完整读取事件触发时的环境。_reactName:原始JSX绑定名(onClick),内部插件系统使用_targetInst:事件目标Fiber,用于内部遍历、事件回放type:业务读取e.type拿到的标准化事件类型(抹平focusin → focus)nativeEvent:持有原生事件引用,所有属性读取底层依赖它target:标准化事件触发DOMcurrentTarget:遍历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()阻止默认行为 - 浏览器兼容补丁:部分老旧浏览器原生
event无timeStamp或值为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/CommandgetModifierState:现代浏览器可正常判断CapsLock/NumLock/ScrollLock,旧浏览器降级支持Shift/Control/Alt/Meta,统一判断锁定键状态,抹平浏览器接口差异button:本次触发的鼠标按键(0左键、1中键、2右键)buttons:当前所有按下的按键掩码(多键同时按住)relatedTarget:原生mouseover/mouseout标准字段relatedTarget,旧版IE不支持,使用fromElement / toElement降级兼容mouseover:relatedTarget= 鼠标离开的元素 =fromElementmouseout:relatedTarget= 鼠标进入的元素 =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 赋值给合成事件
focusin:relatedTarget= 失去焦点的旧元素focusout:relatedTarget= 将要获得焦点的新元素
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获取输入法编辑内容,用于处理中文输入受控组件、输入过滤等场景compositionstart:data为空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无字符含义,统一返回 0getEventCharCode会过滤功能键(回车、方向键等)返回 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/penisPrimary:是否为主触点(多指时第一个按下的手指)
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 - 回车提交无单独按钮:
submitter为null
- 有提交按钮:
javascript
const SubmitEventInterface = {
...EventInterface,
submitter: 0,
};
export const SyntheticSubmitEvent = createSyntheticEvent(SubmitEventInterface);
移动端触摸合成事件构造器:SyntheticTouchEvent
表单提交事件(onTouchStart / onTouchMove / onTouchEnd / onTouchCancel)的公共父类。继承基础事件属性 + UI 事件属性,追加触摸独有的三组触点数组,完整封装多点触摸核心数组
-
抹平移动端浏览器触摸
API细微差异 :底层统一代理原生TouchEvent,iOS/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/deltaY、deltaZ、deltaMode)
-
抹平各浏览器滚轮数值正负、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 的实现差异,输出统一、规范的可打印字符编码,供给 SyntheticKeyboardEvent 的 charCode / which 字段使用
- 现代浏览器支持
charCode(Chrome/Firefox/Safari):优先使用原生charCode,Firefox兼容,按下回车时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.propscreateEventHandle命令式绑定: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向上遍历父链returnlastHostComponent:缓存当前遍历到的宿主DOM节点(HostComponent),用于赋值currentTarget
- 向上遍历
Fiber树:从事件目标Fiber开始,不断取instance.return向上遍历祖先组件,模拟DOM事件冒泡 / 捕获传播路径- 宿主
DOM组件HostComponent / HostHoistable / HostSingleton(div/button/input等真实DOM)- 标记当前
Fiber对应真实DOM,存入lastHostComponent,后续回调currentTarget使用该DOM - 收集
createEventHandle绑定的监听(外部DOM手动绑定):读取DOM节点上缓存的createEventHandle监听列表,匹配原生事件类型、匹配当前捕获 / 冒泡阶段,使用createDispatchListener标准化封装,存入回调数组 - 收集
JSX声明式props绑定(onClick /onClickCapture):调用getListener从Fiber.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节点,过滤无DOM的Fiber - 从
DOM反向取出当前Fiber稳定props - 根据事件名取出
props上绑定的回调 - 内置禁用元素鼠标事件拦截
- 禁用表单元素(
disabled button/input)屏蔽鼠标点击、移入移出事件 - 特定原生标签内置行为屏蔽对应鼠标事件
- 禁用表单元素(
- 事件值类型严格校验,非法值抛运行时错误:如果
listener存在且不是函数,直接抛出明确报错 - 返回最终合法回调