事件类别
事件类型 | 定义 | 文档 |
|
| Event - Web API | MDN |
|
| UIEvent - Web API | MDN |
|
| KeyboardEvent - Web API | MDN |
|
| 鼠标事件 - Web API | MDN |
PointerEvent | PointerEvent 接口代表了由 指针 引发的 DOM 事件的状态,包括接触点的位置,引发事件的设备类型,接触表面受到的压力等。 | PointerEvent - Web API | MDN |
|
| FocusEvent - Web API | MDN |
|
| TouchEvent - Web API | MDN |
|
| DragEvent - Web API | MDN |
|
| WheelEvent - Web API | MDN |
|
| ToggleEvent - Web API | MDN |
TransitonEvent | TransitonEvent 接口指那些提供了与过渡有关信息的事件。 | TransitionEvent - Web API | MDN |
|
| InputEvent - Web API | MDN |
| DOM 接口 | CompositionEvent - Web API | MDN |
| The | AnimationEvent - Web APIs | MDN |
|
| ClipboardEvent - Web API | MDN |
1、SyntheticEvent(基础事件)
SyntheticEvent
是 React 基于 createSyntheticEvent
工厂函数创建的 合成事件基类。
const SyntheticEvent = createSyntheticEvent(EventInterface);
EventInterface 是 React 合成事件系统的基础配置对象,定义了 如何从原生 DOM 事件中提取标准属性 到 React 的合成事件对象。
// EventInterface 是 React 合成事件系统的基础配置对象,定义了 如何从原生 DOM 事件中提取标准属性 到 React 的合成事件对象。
const EventInterface = {
eventPhase: 0,// 表示事件流当前处于哪一个阶段。
bubbles: 0, // Event 接口的 bubbles 只读属性表明事件是否会沿 DOM 树向上冒泡。
cancelable: 0, // Event 实例的只读属性 cancelable 表明该事件是否可以被取消,即事件是否可以像从未发生一样被阻止
timeStamp: function (event: {[propName: string]: mixed}) {
// Event 接口的 timeStamp 只读属性返回事件创建的时间(以毫秒为单位)。
return event.timeStamp || Date.now();
},
defaultPrevented: 0,// 布尔值,表明当前事件是否调用了 event.preventDefault()方法。
isTrusted: 0, // Event 接口的 isTrusted 只读属性是一个表示事件是否由用户行为生成的布尔值。当事件由用户行为触发时,为 true;当事件由脚本创建或修改,或通过 EventTarget.dispatchEvent() 派发时,为 false。
};
工具函数之 createSyntheticEvent
createSyntheticEvent
是 React 合成事件系统的核心工厂函数,用于创建 跨浏览器兼容的合成事件对象。它封装了原生 DOM 事件,提供统一的 API 并处理浏览器差异。
function createSyntheticEvent(Interface: EventInterfaceType) {
// SyntheticBaseEvent 构造函数
function SyntheticBaseEvent(
reactName: string | null,// React 事件名称。
reactEventType: string,//React 事件类型
targetInst: Fiber | null,// 事件目标对应的 Fiber 实例。
nativeEvent: {[propName: string]: mixed, ...},// 原生 DOM 事件对象。
nativeEventTarget: null | EventTarget,// 原生事件的目标元素。
) {
// _reactName:React 事件处理器属性名(如 'onClick')。
this._reactName = reactName;
// _targetInst:事件目标对应的 Fiber 节点(用于事件冒泡)。
this._targetInst = targetInst;
this.type = reactEventType;
this.nativeEvent = nativeEvent;
this.target = nativeEventTarget;
this.currentTarget = null;
// 根据 Interface 提取并标准化属性
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;
}
// 为 SyntheticBaseEvent 的原型添加方法
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 即可阻止默认操作。
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 表示事件不得继续传播。
event.cancelBubble = true;
}
this.isPropagationStopped = functionThatReturnsTrue;
},
// persist 方法:在现代事件系统中,不使用事件池,所以该方法为空。
persist: function () {},
// isPersistent 方法:始终返回 true。
isPersistent: functionThatReturnsTrue,
});
return SyntheticBaseEvent;
}
type EventInterfaceType = {
[propName: string]: 0 | ((event: {[propName: string]: mixed, ...}) => mixed),
};
function functionThatReturnsTrue() {
return true;
}
function functionThatReturnsFalse() {
return false;
}
2、SyntheticUIEvent(UI 原生事件)
SyntheticUIEvent
是 React 内部用于 处理 UI 相关原生事件 的合成事件类,主要解决 浏览器原生 UI 事件(如 focus、scroll、resize) 与 React 事件系统的集成问题。
const SyntheticUIEvent = createSyntheticEvent(UIEventInterface);
const UIEventInterface: EventInterfaceType = {
...EventInterface,
view: 0, // UIEvent.view 只读属性返回 WindowProxy 生成事件的对象。
detail: 0, // UIEvent.detail 是只读属性,当值为非空的时候,提供当前点击数 (和环境有关)
};
3、SyntheticKeyboardEvent(键盘事件)
SyntheticKeyboardEvent
是 React 基于 createSyntheticEvent
工厂函数创建的 键盘事件专用合成事件类。【键盘事件】
const SyntheticKeyboardEvent = createSyntheticEvent(
KeyboardEventInterface,
);
KeyboardEvent - Web API | MDN
const KeyboardEventInterface = {
// 基础事件属性
...UIEventInterface,
// 键盘特定属性
key: getEventKey, // 按下的键(如 "Enter", "a")
code: 0, // 物理键码(如 "KeyA", "Enter")
location: 0, // 键的位置(0=标准, 1=左, 2=右, 3=数字小键盘)
ctrlKey: 0, // Ctrl 键是否按下
shiftKey: 0, // Shift 键是否按下
altKey: 0, // Alt 键是否按下
metaKey: 0, // Meta 键(如 Windows 键)是否按下
repeat: 0, // 是否为重复按键
locale: 0,
getModifierState: getEventModifierState,
// 字符编码
charCode: function (event: {[propName: string]: mixed}) {
if (event.type === 'keypress') {
// 从 KeyboardEvent 对象中提取字符编码。
return getEventCharCode( event );
}
return 0;
},
// 键码
keyCode: function (event: {[propName: string]: mixed}) {
if (event.type === 'keydown' || event.type === 'keyup') {
return event.keyCode;
}
return 0;
},
// 统一 keyCode 和 charCode
which: function (event: {[propName: string]: mixed}) {
if (event.type === 'keypress') {
return getEventCharCode( event );
}
if (event.type === 'keydown' || event.type === 'keyup') {
return event.keyCode;
}
return 0;
},
};
const UIEventInterface: EventInterfaceType = {
...EventInterface,
view: 0, // UIEvent.view 只读属性返回 WindowProxy 生成事件的对象。
detail: 0, // UIEvent.detail 是只读属性,当值为非空的时候,提供当前点击数 (和环境有关)
};
工具函数之 getEventModifierState
function getEventModifierState(nativeEvent: {[propName: string]: mixed}) {
return modifierStateGetter;
}
工具函数之 modifierStateGetter
modifierStateGetter
是 React 用于 查询键盘修饰键状态 的工具函数。
function modifierStateGetter(keyArg) {
const syntheticEvent = this;
// 从合成事件获取原生事件对象
const nativeEvent = syntheticEvent.nativeEvent;
// KeyboardEvent 对象上的 getModifierState() 方法。getModifierState() 方法允许你查询一个特定的修饰键是否被激活。
if (nativeEvent.getModifierState) {
return nativeEvent.getModifierState(keyArg);
}
// 过映射表(modifierKeyToProp)查找对应属性
const keyProp = modifierKeyToProp[keyArg];
return keyProp ? !!nativeEvent[keyProp] : false;
}
const modifierKeyToProp = {
Alt: 'altKey',
Control: 'ctrlKey',
Meta: 'metaKey',
Shift: 'shiftKey',
};
工具函数之 getEventCharCode
从 KeyboardEvent
对象中提取字符编码。由于不同浏览器在处理键盘事件时,对于字符编码的存储和返回方式存在差异,这个函数会对这些差异进行处理,最终返回一个有效的字符编码,如果是不可打印字符(除了回车键)则返回 0。
function getEventCharCode(nativeEvent: KeyboardEvent): number {
// 声明变量 charCode 用于存储最终要返回的字符编码。
let charCode;
// 从 nativeEvent 中获取 keyCode,keyCode 是按键的代码,不同的按键有不同的 keyCode 值。
const keyCode = nativeEvent.keyCode;
// 检查 nativeEvent 对象是否包含 charCode 属性。
if ('charCode' in nativeEvent) {
// 如果包含,将 nativeEvent.charCode 的值赋给 charCode。
charCode = nativeEvent.charCode;
//对于 Firefox 浏览器,它在回车键按下时不会设置 charCode,所以当 charCode 为 0 且 keyCode 为 13(回车键的 keyCode)时,将 charCode 设置为 13。
// FF does not set `charCode` for the Enter-key, check against `keyCode`.
if (charCode === 0 && keyCode === 13) {
charCode = 13;
}
} else {
// 如果不包含,说明可能是 IE8 浏览器,它没有实现 charCode 属性,此时直接将 keyCode 的值赋给 charCode。
// IE8 does not implement `charCode`, but `keyCode` has the correct value.
charCode = keyCode;
}
// IE and Edge (on Windows) and Chrome / Safari (on Windows and Linux)
// report Enter as charCode 10 when ctrl is pressed.
// 在某些浏览器(如 Windows 上的 IE、Edge 以及 Windows 和 Linux 上的 Chrome、Safari)中,当按下 Ctrl 键和回车键时,charCode 会被报告为 10,这里将其修正为 13。
if (charCode === 10) {
charCode = 13;
}
// Some non-printable keys are reported in `charCode`/`keyCode`, discard them.
// Must not discard the (non-)printable Enter-key.
// 如果 charCode 大于等于 32 或者等于 13(回车键),说明是可打印字符或者回车键,返回该 charCode;
if (charCode >= 32 || charCode === 13) {
return charCode;
}
// 否则,认为是不可打印字符,返回 0。
return 0;
}
4、SyntheticFocusEvent(焦点事件)
SyntheticFocusEvent
是 React 基于 createSyntheticEvent
工厂函数创建的 焦点事件专用合成事件类。
const SyntheticFocusEvent = createSyntheticEvent(FocusEventInterface);
const FocusEventInterface: EventInterfaceType = {
// 基础事件属性
...UIEventInterface,
relatedTarget: 0, // 失去/获得焦点时的相关元素
};
5、SyntheticMouseEvent(鼠标事件)
SyntheticMouseEvent
是 React 基于 createSyntheticEvent
工厂函数创建的 鼠标事件专用合成事件类。
const SyntheticMouseEvent = createSyntheticEvent(MouseEventInterface);
const MouseEventInterface: EventInterfaceType = {
...UIEventInterface,
screenX: 0, // 屏幕坐标 X
screenY: 0, // 屏幕坐标 Y
clientX: 0, // 客户端坐标 X
clientY: 0, // 客户端坐标 Y
pageX: 0, // 页面坐标 X
pageY: 0, // 页面坐标 Y
ctrlKey: 0, // Ctrl 键是否按下
shiftKey: 0, // Shift 键是否按下
altKey: 0, // Alt 键是否按下
metaKey: 0, // Meta 键是否按下
getModifierState: getEventModifierState,
button: 0, // 按下的按钮(0=主按钮,1=中键,2=辅助按钮)
buttons: 0, // 当前按下的按钮掩码
relatedTarget: function (event) {
// 旧版浏览器(如 IE)使用 fromElement/toElement
if (event.relatedTarget === undefined)
return event.fromElement === event.srcElement
? event.toElement // mouseleave 场景
: event.fromElement; // mouseenter 场景
// 现代浏览器直接返回 relatedTarget
return event.relatedTarget;
},
// 相对上次x坐标的位移
movementX: function (event) {
if ('movementX' in event) {
return event.movementX;
}
updateMouseMovementPolyfillState(event);
return lastMovementX;
},
// 相对于上次坐标y轴位移
movementY: function (event) {
if ('movementY' in event) {
return event.movementY;
}
return lastMovementY;
},
};
MouseEvent 接口的 movementX
只读属性提供了给定事件与前一个 mousemove 事件之间鼠标指针在 X 坐标轴上的移动值。换句话说,该属性的值计算如下:currentEvent.movementX = currentEvent.screenX - previousEvent.screenX
。
MouseEvent 接口的 movementY
只读属性提供了当前事件和上一个 mousemove 事件之间鼠标指针在 Y 坐标轴上的移动值。换句话说,这个值是这样计算的:currentEvent.movementY = currentEvent.screenY - previousEvent.screenY
。
工具函数之 updateMouseMovementPolyfillState
updateMouseMovementPolyfillState
是 React 为 不支持原生 movementX/Y
属性的浏览器 提供的位移计算函数。
let lastMovementX;
let lastMovementY;
let lastMouseEvent;
function updateMouseMovementPolyfillState(event: {[propName: string]: mixed}) {
// 检查当前事件是否与上一次事件相同。若相同则跳过计算(避免重复处理)
if (event !== lastMouseEvent) {
// 若存在上一次事件且当前为 mousemove 事件
if (lastMouseEvent && event.type === 'mousemove') {
lastMovementX = event.screenX - lastMouseEvent.screenX;
lastMovementY = event.screenY - lastMouseEvent.screenY;
} else {
// 重置位移
lastMovementX = 0;
lastMovementY = 0;
}
// 保存当前事件引用
lastMouseEvent = event;
}
}
6、SyntheticDragEvent(拖拽事件)
SyntheticDragEvent
是 React 基于 createSyntheticEvent
工厂函数创建的 拖拽事件专用合成事件类。
const SyntheticDragEvent = createSyntheticEvent(DragEventInterface);
const DragEventInterface: EventInterfaceType = {
...MouseEventInterface,
dataTransfer: 0,// 拖拽数据对象(包含拖拽内容、类型等)
};
7、SyntheticPointerEvent(指针事件)
Pointer Events
SyntheticPointerEvent
是 React 基于 createSyntheticEvent
工厂函数创建的 指针事件专用合成事件类。
const SyntheticPointerEvent: $FlowFixMe = createSyntheticEvent(
PointerEventInterface,
);
const PointerEventInterface = {
...MouseEventInterface,
pointerId: 0, // 指针唯一标识符
width: 0, // 接触面积的宽度(像素)
height: 0, // 接触面积的高度(像素)
pressure: 0, // 压力值(0-1,鼠标通常为 0.5)
tangentialPressure: 0, // 压力值
tiltX: 0, // 指针在 X 轴的倾斜角度
tiltY: 0, // 指针相对于视口的 Y 坐标
twist: 0, // 传感器(笔)顺时针旋转角度
pointerType: 0, // 指针类型('mouse'/'touch'/'pen')
isPrimary: 0, // 是否为主指针(多点触控时)
};
8、SyntheticTouchEvent(触摸事件)
SyntheticTouchEvent
是 React 基于 createSyntheticEvent
工厂函数创建的 触摸事件专用合成事件类。
const SyntheticTouchEvent = createSyntheticEvent(TouchEventInterface);
const TouchEventInterface = {
...UIEventInterface,
// 触摸特定属性
touches: 0, // 当前屏幕上所有触摸点的列表
targetTouches: 0, // 与当前目标元素相关的触摸点列表
changedTouches: 0, // 与当前事件相关的触摸点列表
altKey: 0, // Alt 键是否按下
metaKey: 0, // Meta 键是否按下
ctrlKey: 0, // Ctrl 键是否按下
shiftKey: 0, // Shift 键是否按下
getModifierState: getEventModifierState,
};
9、SyntheticTransitionEvent(css 过渡事件)
CSS Transitions
SyntheticTransitionEvent
是 React 基于 createSyntheticEvent
工厂函数创建的 CSS 过渡事件专用合成事件类。
const SyntheticTransitionEvent = createSyntheticEvent(TransitionEventInterface);
const TransitionEventInterface = {
...EventInterface,
propertyName: 0, // 发生过渡的 CSS 属性名称
elapsedTime: 0, // 过渡已运行的时间(秒)
pseudoElement: 0, // 过渡发生的伪元素(如 '::before')
};
10、SyntheticWheelEvent(滚轮事件)
UI Events
SyntheticWheelEvent
是 React 基于 createSyntheticEvent
工厂函数创建的 滚轮事件专用合成事件类
const SyntheticWheelEvent = createSyntheticEvent(WheelEventInterface);
const WheelEventInterface = {
...MouseEventInterface,
// 水平滚动量
deltaX(event: {[propName: string]: mixed}) {
return 'deltaX' in event
// WheelEvent.deltaX 只读属性是一个表示以 WheelEvent.deltaMode 为单位的水平滚动量的 double 值。
? event.deltaX
:
'wheelDeltaX' in event
?
-event.wheelDeltaX
: 0;
},
// 垂直滚动量
deltaY(event: {[propName: string]: mixed}) {
return 'deltaY' in event
? event.deltaY
:
'wheelDeltaY' in event
?
-event.wheelDeltaY
:
'wheelDelta' in event
?
-event.wheelDelta
: 0;
},
// 深度滚动量(极少使用)
deltaZ: 0,
// 滚动单位(0=像素,1=行,2=页)
// WheelEvent.deltaMode 只读属性返回 unsigned long,表示滚动量的 delta 值的单位。
deltaMode: 0,
};
11、SyntheticToggleEvent(切换事件)
SyntheticToggleEvent
是 React 基于 createSyntheticEvent
工厂函数创建的 切换事件专用合成事件类。
const SyntheticToggleEvent = createSyntheticEvent(ToggleEventInterface);
const ToggleEventInterface = {
...EventInterface,
newState: 0, // 新状态
oldState: 0, // 旧状态
};
12、SyntheticCompositionEvent(组合事件)
SyntheticCompositionEvent
是 React 内部用于 处理输入法组合文本事件 的合成事件类,主要解决 非直接文本输入(如拼音、日文假名输入) 的捕获和处理问题。
const SyntheticCompositionEvent: $FlowFixMe = createSyntheticEvent(
CompositionEventInterface,
);
const CompositionEventInterface: EventInterfaceType = {
...EventInterface,
data: 0, // 组合文本内容
};
13、SyntheticInputEvent(输入事件)
React 事件系统中一个有趣的设计:将合成输入事件(SyntheticInputEvent)与合成组合事件(SyntheticCompositionEvent)复用同一实现。
const SyntheticInputEvent = SyntheticCompositionEvent;
14、SyntheticAnimationEvent(动画事件)
React 中创建 合成动画事件(Synthetic Animation Event) 的核心逻辑,主要通过 createSyntheticEvent
工厂函数生成统一的动画事件抽象。
const SyntheticAnimationEvent: $FlowFixMe = createSyntheticEvent(
AnimationEventInterface,
);
const AnimationEventInterface: EventInterfaceType = {
...EventInterface,
// animationName:触发动画的 CSS 动画名称。
animationName: 0,
// elapsedTime:动画已经运行的时间(秒)。
elapsedTime: 0,
// pseudoElement:动画应用的伪元素(如 ::before、::after)。
pseudoElement: 0,
};
15、SyntheticClipboardEvent(剪贴板事件)
React 中创建 合成剪贴板事件(Synthetic Clipboard Event) 的核心逻辑,通过 createSyntheticEvent
工厂函数生成统一的剪贴板事件抽象,用于处理剪切、复制、粘贴等操作。
const SyntheticClipboardEvent: $FlowFixMe = createSyntheticEvent(
ClipboardEventInterface,
);
const ClipboardEventInterface: EventInterfaceType = {
...EventInterface,
// 用于访问剪贴板内容,包含文本、文件等数据。
clipboardData: function (event) {
return 'clipboardData' in event
? event.clipboardData
: window.clipboardData;
},
};
事件插件系统
React 的事件系统通过 事件插件(Event Plugins) 实现了跨浏览器兼容、合成事件和事件委托等核心特性。这些插件是 React 事件系统的重要组成部分,负责处理不同类型的原生事件并将其转换为统一的合成事件。
事件插件系统 | 含义 |
SimpleEventPlugin | 处理基本的 DOM 事件(如 |
EnterLeaveEventPlugin | 处理鼠标进入 / 离开事件( |
ChangeEventPlugin | 处理表单元素的变更事件(如 |
SelectEventPlugin | 处理文本选择事件(如 |
BeforeInputEventPlugin | 处理输入前事件( |
FormActionEventPlugin | 处理表单提交。 |
1、SimpleEventPlugin.extractEvents
extractEvents
函数是 React 事件系统中用于从原生 DOM 事件中提取相关信息,创建合成事件对象,并将合成事件和对应的事件监听器添加到调度队列中的核心函数。
它会根据不同的原生事件类型,选择合适的合成事件构造函数,还会根据事件系统标志和捕获阶段等条件,积累相应的事件监听器,最终将合成事件和监听器组合成一个对象添加到调度队列中。
函数参数含义:
domEventName
:原生 DOM 事件的名称,如'click'
、'keydown'
等。targetInst
:事件目标对应的 Fiber 实例。nativeEvent
:原生的 DOM 事件对象。nativeEventTarget
:原生事件的目标元素。eventSystemFlags
:事件系统的标志,用于表示事件的状态和特性。targetContainer
:事件发生的目标容器。
function extractEvents(
dispatchQueue: DispatchQueue,
domEventName: DOMEventName,
targetInst: null | Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: null | EventTarget,
eventSystemFlags: EventSystemFlags,
targetContainer: EventTarget,
): void {
// 通过 topLevelEventsToReactNames 映射表获取原生 DOM 事件对应的 React 事件名称。
const reactName = topLevelEventsToReactNames.get(domEventName);
// 如果没有对应的 React 事件名称,则直接返回,不进行后续处理。
if (reactName === undefined) {
return;
}
// 初始化合成事件构造函数和事件类型
let SyntheticEventCtor = SyntheticEvent;
let reactEventType: string = domEventName;
// 省略代码
// switch(){}
// 是否处于捕获节点
const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
// 非托管节点,处理非 React 管理的 DOM 节点(如第三方库创建的节点)上的事件。
if (
enableCreateEventHandleAPI &&
eventSystemFlags & IS_EVENT_HANDLE_NON_MANAGED_NODE
) {
const listeners = accumulateEventHandleNonManagedNodeListeners(
((reactEventType: any): DOMEventName),
targetContainer,
inCapturePhase,
);
if (listeners.length > 0) {
const event: ReactSyntheticEvent = new SyntheticEventCtor(
reactName,
reactEventType,
null,
nativeEvent,
nativeEventTarget,
);
dispatchQueue.push({event, listeners});
}
} else {
// react节点
const accumulateTargetOnly =
!inCapturePhase &&
(domEventName === 'scroll' || domEventName === 'scrollend');
const listeners = accumulateSinglePhaseListeners(
targetInst,// 目标 Fiber
reactName,// React 事件名(如 'onClick')
nativeEvent.type,// 原生事件类型
inCapturePhase,// 是否捕获阶段
accumulateTargetOnly,// 是否仅目标节点
nativeEvent,// 原生事件对象
);
if (listeners.length > 0) {
const event: ReactSyntheticEvent = new SyntheticEventCtor(
reactName,
reactEventType,
null,
nativeEvent,
nativeEventTarget,
);
dispatchQueue.push({event, listeners});
}
}
}
// 根据不同的原生事件类型选择合成事件构造函数
switch (domEventName) {
/** 键盘事件 */
case 'keypress':
if (getEventCharCode(((nativeEvent: any): KeyboardEvent)) === 0) {
return;
}
case 'keydown':
case 'keyup':
SyntheticEventCtor = SyntheticKeyboardEvent;
break;
/** 焦点事件 */
case 'focusin':
// 浏览器特定事件名(如 focusin)转为 React 标准名(focus)。
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 'toggle':
case 'beforetoggle':
SyntheticEventCtor = SyntheticToggleEvent;
break;
default:
break;
}
工具函数之 accumulateEventHandleNonManagedNodeListeners
用于收集非托管节点(non-managed node)事件监听器的核心函数。
accumulateEventHandleNonManagedNodeListeners
的主要作用是:
- 从指定的 DOM 节点(
currentTarget
)上收集特定类型的事件监听器。 - 这些监听器是直接绑定在 DOM 节点上的,而非通过 React 合成事件系统注册的(即 “非托管节点”)。
- 收集后的监听器会被包装成
DispatchListener
对象。
函数参数含义:
reactEventType
: React 内部事件名称(如click
、change
)。currentTarget
: 事件触发的 DOM 节点。inCapturePhase
: 是否处于事件捕获阶段(true
表示捕获阶段,false
表示冒泡阶段)。
function accumulateEventHandleNonManagedNodeListeners(
reactEventType: DOMEventName,
currentTarget: EventTarget,
inCapturePhase: boolean,
): Array<DispatchListener> {
// 初始化一个空数组,用于存储匹配的事件监听器
const listeners: Array<DispatchListener> = [];
// 从 currentTarget 节点获取所有事件监听器
// getEventHandlerListeners 用于从 DOM 节点获取所有注册的事件监听器。这些监听器可能是通过 React 注册的,也可能是直接通过 addEventListener 注册的。
const eventListeners = getEventHandlerListeners(currentTarget);
// 如果该节点上有注册的事件监听器
if (eventListeners !== null) {
eventListeners.forEach(entry => {
// 筛选出与当前事件类型和阶段匹配的监听器
if (entry.type === reactEventType && entry.capture === inCapturePhase) {
// 将符合条件的监听器包装成 DispatchListener 对象并添加到结果数组中
listeners.push(
// 使用 createDispatchListener 包装每个监听器
createDispatchListener(null, entry.callback, currentTarget),
);
}
});
}
return listeners;
}
工具函数之 getEventHandlerListeners
getEventHandlerListeners
是 React 内部用于 获取事件监听器集合 的工具函数。
React 在注册事件监听器时,会将监听器集合存储在作用域对象或 DOM 元素的 internalEventHandlerListenersKey
属性中。
function getEventHandlerListeners(
scope: EventTarget | ReactScopeInstance,
): null | Set<ReactDOMEventHandleListener> {
// 从 作用域对象(ReactScopeInstance)或 DOM 元素(EventTarget)中提取注册的事件监听器
return (scope: any)[internalEventHandlerListenersKey] || null;
}
const internalEventHandlerListenersKey = '__reactListeners$' + randomKey;
type ReactDOMEventHandleListener = {
callback: (SyntheticEvent<EventTarget>) => void, // 事件处理函数
capture: boolean, // 是否为捕获阶段
type: DOMEventName, // 事件类型(如 'click')
};
工具函数之 createDispatchListener
createDispatchListener
是 React 事件系统中用于 创建事件监听器包装对象 的工具函数。
function createDispatchListener(
instance: null | Fiber, //事件监听器所在的 Fiber 节点(用于定位组件)
listener: Function, // 实际的事件处理函数(如 onClick 回调)
currentTarget: EventTarget, // 事件绑定的 DOM 元素(如 <button>)
): DispatchListener {
return {
instance,
listener,
currentTarget,
};
}
// 返回值结构
{
instance: Fiber, // 组件对应的 Fiber 节点
listener: Function, // 事件处理函数
currentTarget: Element // 事件当前目标 DOM 元素
}
工具函数之 accumulateSinglePhaseListeners
accumulateSinglePhaseListeners
是 React 事件系统中 负责收集事件监听器 的核心函数,它通过遍历 Fiber 树,从目标节点到根节点收集与当前事件相关的所有监听器(包括捕获和冒泡阶段)。
函数参数含义:
targetFiber
: 事件触发的目标 Fiber 节点。reactName
: React 事件名(如onClick
)。nativeEventType
: 原生事件类型(如click
)。inCapturePhase
: 是否处于捕获阶段(true
为捕获,false
为冒泡)。accumulateTargetOnly
: 是否仅收集目标节点的监听器。nativeEvent
: 原生 DOM 事件对象。
function accumulateSinglePhaseListeners(
targetFiber: Fiber | null,// 事件目标 Fiber 节点
reactName: string | null,// React 事件名(如 'onClick')
nativeEventType: string, // 原生事件类型(如 'click')
inCapturePhase: boolean,// 是否处于捕获阶段
accumulateTargetOnly: boolean,// 是否仅收集目标节点
nativeEvent: AnyNativeEvent, // 原生事件对象
): Array<DispatchListener> {
// 捕获阶段:事件名添加 Capture 后缀(如 onClickCapture)。
const captureName = reactName !== null ? reactName + 'Capture' : null;
// react事件名称,冒泡阶段:使用原始事件名(如 onClick)
const reactEventName = inCapturePhase ? captureName : reactName;
let listeners: Array<DispatchListener> = [];
let instance = targetFiber;
let lastHostComponent = null;
// Accumulate all instances and listeners via the target -> root path.
// 遍历方向:从 targetFiber 开始,通过 instance.return 逐级向上访问父节点,直到根节点。
while (instance !== null) {
const {stateNode, tag} = instance;
// HostComponent:处理真实 DOM 节点上的监听器。
if (
(tag === HostComponent ||
tag === HostHoistable ||
tag === HostSingleton) &&
stateNode !== null
) {
lastHostComponent = stateNode;
// 1. 处理 createEventHandle 的监听器
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),
),
);
}
});
}
}
// 2. 处理标准 React 监听器(如 onClick)
if (reactEventName !== null) {
const listener = getListener(instance, reactEventName);
if (listener != null) {
listeners.push(
createDispatchListener(instance, listener, lastHostComponent),
);
}
}
// 3. 处理 ScopeComponent 的监听器(实验性 API)
} else if (
enableCreateEventHandleAPI &&
enableScopeAPI &&
tag === ScopeComponent &&
lastHostComponent !== null &&
stateNode !== null
) {
// Scopes
// 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;
}
2、EnterLeaveEventPlugin.extractEvents
extractEvents
是 React 事件系统中处理 鼠标 / 指针进入离开事件 的核心函数。
function extractEvents(
dispatchQueue: DispatchQueue,
domEventName: DOMEventName,
targetInst: null | Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: null | EventTarget,
eventSystemFlags: EventSystemFlags,
targetContainer: EventTarget,
) {
// 进入事件
const isOverEvent = domEventName === 'mouseover' || domEventName === 'pointerover';
// 离开事件
const isOutEvent = domEventName === 'mouseout' || domEventName === 'pointerout';
// 过滤无效事件
if (isOverEvent && !isReplayingEvent(nativeEvent)) {
// 目标元素
const related = (nativeEvent: any).relatedTarget || (nativeEvent: any).fromElement;
if (related) {
// 从 DOM 节点查找最近的 Fiber 实例 的核心函数
if (
getClosestInstanceFromNode(related) ||
isContainerMarkedAsRoot(related)
) {
return;
}
}
}
if (!isOutEvent && !isOverEvent) {
// Must not be a mouse or pointer in or out - ignoring.
return;
}
// 获取window对象
let win;
if ((nativeEventTarget: any).window === nativeEventTarget) {
// `nativeEventTarget` is probably a window object.
win = nativeEventTarget;
} else {
const doc = (nativeEventTarget: any).ownerDocument;
if (doc) {
win = doc.defaultView || doc.parentWindow;
} else {
win = window;
}
}
// 当前正要离开的元素
let from;
// 要进入的元素
let to;
// 离开事件(mouseout/pointerout)
if (isOutEvent) {
// 获取目标元素
const related = nativeEvent.relatedTarget || (nativeEvent: any).toElement;
// 离开的元素(当前 Fiber 节点)
from = targetInst;
// 鼠标进入的元素(Fiber 节点),需通过 getClosestInstanceFromNode 从 DOM 节点获取对应的 Fiber。
to = related ? getClosestInstanceFromNode((related: any)) : null;
if (to !== null) {
const nearestMounted = getNearestMountedFiber(to);
const tag = to.tag;
// 过滤未挂载或非宿主组件的节点
if (
to !== nearestMounted ||
(tag !== HostComponent && tag !== HostSingleton && tag !== HostText)
) {
to = null;
}
}
} else {
// Moving to a node from outside the window.
from = null;
to = targetInst;
}
if (from === to) {
// Nothing pertains to our managed components.
return;
}
/** 根据事件类型选择合成事件构造函数 */
// 默认鼠标事件
let SyntheticEventCtor = SyntheticMouseEvent;
let leaveEventType = 'onMouseLeave';
let enterEventType = 'onMouseEnter';
let eventTypePrefix = 'mouse';
// 指针事件
if (domEventName === 'pointerout' || domEventName === 'pointerover') {
SyntheticEventCtor = SyntheticPointerEvent;
leaveEventType = 'onPointerLeave';
enterEventType = 'onPointerEnter';
eventTypePrefix = 'pointer';
}
/** 将 React Fiber 节点转换为实际 DOM 节点 */
// 要离开的元素
const fromNode = from == null ? win : getNodeFromInstance(from);
// 要进入的元素
const toNode = to == null ? win : getNodeFromInstance(to);
// 创建 leave 事件对象
const leave: KnownReactSyntheticEvent = new SyntheticEventCtor(
leaveEventType, // 'onMouseLeave' 或 'onPointerLeave'
eventTypePrefix + 'leave', // 'mouseleave' 或 'pointerleave'
from, // 源 Fiber 节点
nativeEvent, // 原生事件对象
nativeEventTarget, // 原生事件目标
);
leave.target = fromNode; // 事件源 DOM 节点
leave.relatedTarget = toNode; // 鼠标移向的节点
// 创建 enter 事件对象
let enter: KnownReactSyntheticEvent | null = null;
const nativeTargetInst = getClosestInstanceFromNode((nativeEventTarget: any));
// 仅当原生事件目标与当前处理的 Fiber 节点匹配时创建 enter 事件
if (nativeTargetInst === targetInst) {
const enterEvent: KnownReactSyntheticEvent = new SyntheticEventCtor(
enterEventType,
eventTypePrefix + 'enter',
to,
nativeEvent,
nativeEventTarget,
);
// 设置事件对象属性
enterEvent.target = toNode;
enterEvent.relatedTarget = fromNode;
enter = enterEvent;
}
// 构建事件派发队列
accumulateEnterLeaveTwoPhaseListeners(dispatchQueue, leave, enter, from, to);
}
type BaseSyntheticEvent = {
isPersistent: () => boolean,
isPropagationStopped: () => boolean,
_dispatchInstances?: null | Array<Fiber | null> | Fiber,
_dispatchListeners?: null | Array<Function> | Function,
_targetInst: Fiber,
nativeEvent: Event,
target?: mixed,
relatedTarget?: mixed,
type: string,
currentTarget: null | EventTarget,
};
type KnownReactSyntheticEvent = BaseSyntheticEvent & {
_reactName: string,
};
工具函数之 accumulateEnterLeaveTwoPhaseListeners
accumulateEnterLeaveTwoPhaseListeners
是 React 事件系统中处理 鼠标 / 指针进入离开事件 的核心函数。
函数参数含义:
dispatchQueue
: 事件派发队列,用于存储待执行的事件监听器。leaveEvent
: 离开事件对象(如onMouseLeave
)。enterEvent
: 进入事件对象(如onMouseEnter
)。from
: 鼠标离开的元素对应的 Fiber 节点。to
: 鼠标进入的元素对应的 Fiber 节点。
function accumulateEnterLeaveTwoPhaseListeners(
dispatchQueue: DispatchQueue, // 事件派发队列
leaveEvent: KnownReactSyntheticEvent, //离开事件对象
enterEvent: null | KnownReactSyntheticEvent, // 进入事件对象
from: Fiber | null, //鼠标离开的元素对应的 Fiber
to: Fiber | null, // 鼠标进入的元素对应的 Fiber
): void {
// 寻找共同祖先节点
const common = from && to ? getLowestCommonAncestor(from, to) : null;
// 收集离开事件监听器(冒泡阶段)
if (from !== null) {
// 遍历方向:从 from 节点开始,向上遍历到 common 节点(不包含 common)。
accumulateEnterLeaveListenersForEvent(
dispatchQueue,
leaveEvent,
from,
common,
false, // 冒泡阶段(从内向外)
);
}
// 收集进入事件监听器(捕获阶段)
if (to !== null && enterEvent !== null) {
// 遍历方向:从 common 节点(包含)开始,向下遍历到 to 节点。
accumulateEnterLeaveListenersForEvent(
dispatchQueue,
enterEvent,
to,
common,
true, // 捕获阶段(从外向内)
);
}
}
工具函数之 getLowestCommonAncestor
getLowestCommonAncestor
是 React 内部用于 查找两个 Fiber 节点最近公共祖先 的核心算法。
function getLowestCommonAncestor(instA: Fiber, instB: Fiber): Fiber | null {
let nodeA: null | Fiber = instA;
let nodeB: null | Fiber = instB;
/** 计算节点深度 */
let depthA = 0;
// getParent(跳过非宿主组件,直接定位父宿主组件)
for (let tempA: null | Fiber = nodeA; tempA; tempA = getParent(tempA)) {
depthA++;
}
let depthB = 0;
for (let tempB: null | Fiber = nodeB; tempB; tempB = getParent(tempB)) {
depthB++;
}
/** 对齐深度差 */
// If A is deeper, crawl up.
while (depthA - depthB > 0) {
nodeA = getParent(nodeA);
depthA--;
}
// If B is deeper, crawl up.
while (depthB - depthA > 0) {
nodeB = getParent(nodeB);
depthB--;
}
// Walk in lockstep until we find a match.
let depth = depthA;
/** 同步向上查找公共祖先 */
while (depth--) {
// 终止条件:
// 找到相同节点(nodeA === nodeB)
// 找到当前节点与备用节点(alternate)匹配(支持双缓存机制)
if (nodeA === nodeB || (nodeB !== null && nodeA === nodeB.alternate)) {
return nodeA;
}
nodeA = getParent(nodeA);
nodeB = getParent(nodeB);
}
return null;
}
工具函数之 getParent
getParent
是 React 内部用于 查找指定 Fiber 节点的最近宿主组件父节点 的工具函数。
function getParent(inst: Fiber | null): Fiber | null {
if (inst === null) {
return null;
}
// 向上遍历 Fiber 树(通过 return 指针)
do {
inst = inst.return;
// 终止条件:
// 找到 HostComponent(普通 DOM 元素)或 HostSingleton(特殊 DOM 元素,如 <input>)
// 或遍历到根节点(inst 变为 null)
} while (inst && inst.tag !== HostComponent && inst.tag !== HostSingleton);
if (inst) {
return inst;
}
return null;
}
工具函数之 accumulateEnterLeaveListenersForEvent
accumulateEnterLeaveListenersForEvent
是 React 事件系统中处理 鼠标 / 指针进入离开事件 的核心函数。
函数参数含义:
dispatchQueue
: 事件派发队列,存储待执行的事件监听器。event
: 合成事件对象(如onMouseLeave
)。target
: 事件目标 Fiber 节点(鼠标离开 / 进入的元素)。common
: 最近公共祖先节点,遍历终止条件。inCapturePhase
: 是否为捕获阶段(true
表示捕获,false
表示冒泡)。
function accumulateEnterLeaveListenersForEvent(
dispatchQueue: DispatchQueue, // 事件派发队列
event: KnownReactSyntheticEvent, // 合成事件对象
target: Fiber, // 事件目标 Fiber 节点
common: Fiber | null, //公共祖先节点
inCapturePhase: boolean, // 是否为捕获阶段
): void {
// 事件注册名(如 'onMouseLeave')
const registrationName = event._reactName;
// 存储收集到的监听器
const listeners: Array<DispatchListener> = [];
let instance: null | Fiber = target;
while (instance !== null) {
// 到达公共祖先节点,停止遍历
if (instance === common) {
break;
}
const {alternate, stateNode, tag} = instance;
if (alternate !== null && alternate === common) {
break;
}
// 处理宿主组件(DOM 元素)
if (
(tag === HostComponent ||
tag === HostHoistable ||
tag === HostSingleton) &&
stateNode !== null
) {
const currentTarget = stateNode;
// 捕获阶段
if (inCapturePhase) {
const captureListener = getListener(instance, registrationName);
if (captureListener != null) {
// unshift:将监听器插入队列头部,确保从外向内执行(祖先 → 目标)。
listeners.unshift(
createDispatchListener(instance, captureListener, currentTarget),
);
}
} else if (!inCapturePhase) {
// 冒泡阶段=
const bubbleListener = getListener(instance, registrationName);
if (bubbleListener != null) {
// push:将监听器插入队列尾部,确保从内向外执行(目标 → 祖先)。
listeners.push(
createDispatchListener(instance, bubbleListener, currentTarget),
);
}
}
}
instance = instance.return;
}
if (listeners.length !== 0) {
// 加入派发队列
dispatchQueue.push({event, listeners});
}
}
工具函数之 getListener
getListener
是 React 事件系统中用于 从 Fiber 节点获取事件监听器 的核心函数。
function getListener(
inst: Fiber,
registrationName: string,
): Function | null {
// 获取 Fiber 对应的 DOM 节点(stateNode)
const stateNode = inst.stateNode;
if (stateNode === null) {
// Work in progress (ex: onload events in incremental mode).
return null;
}
// 从 DOM 节点获取当前组件的 props
const props = getFiberCurrentPropsFromNode(stateNode);
if (props === null) {
// Work in progress.
return null;
}
// 从 props 中提取事件监听器(如 props.onClick)
const listener = props[registrationName];
// 若元素禁用且为鼠标事件,返回 null(阻止执行)
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;
}
工具函数之 getFiberCurrentPropsFromNode
getFiberCurrentPropsFromNode
是 React 内部用于 从 DOM 节点或 Suspense 实例获取对应 Fiber 节点的 props 的工具函数。
function getFiberCurrentPropsFromNode(
node: Instance | TextInstance | SuspenseInstance,
): Props {
return (node: any)[internalPropsKey] || null;
}
const internalPropsKey = '__reactProps$' + randomKey;
工具函数之 shouldPreventMouseEvent
shouldPreventMouseEvent
是 React 内部用于 判断是否阻止鼠标事件默认行为 的工具函数。
function shouldPreventMouseEvent(
name: string,
type: string,
props: Props,
): boolean {
switch (name) {
case 'onClick':
case 'onClickCapture':
case 'onDoubleClick':
case 'onDoubleClickCapture':
case 'onMouseDown':
case 'onMouseDownCapture':
case 'onMouseMove':
case 'onMouseMoveCapture':
case 'onMouseUp':
case 'onMouseUpCapture':
case 'onMouseEnter':
// isInteractive 判断元素是否可交互的
return !!(props.disabled && isInteractive(type));
default:
return false;
}
}
工具函数之 isReplayingEvent
// 全局变量 currentReplayingEvent,用于标记当前正在重放的原生事件
let currentReplayingEvent = null;
// 判断某个原生事件是否正在被重放。
function isReplayingEvent(event: AnyNativeEvent): boolean {
return event === currentReplayingEvent;
}
// 标记某个原生事件开始重放。
function setReplayingEvent(event: AnyNativeEvent): void {
currentReplayingEvent = event;
}
// 清除重放标记。
function resetReplayingEvent(): void {
currentReplayingEvent = null;
}
工具函数之 getNodeFromInstance
获取 fiber 节点的实际 DOM 节点。
function getNodeFromInstance(inst: Fiber): Instance | TextInstance {
const tag = inst.tag;
if (
tag === HostComponent ||
tag === HostHoistable ||
tag === HostSingleton ||
tag === HostText
) {
// In Fiber this, is just the state node right now. We assume it will be
// a host component or host text.
return inst.stateNode;
}
// Without this first invariant, passing a non-DOM-component triggers the next
// invariant for a missing parent, which is super confusing.
throw new Error('getNodeFromInstance: Invalid argument.');
}
3、ChangeEventPlugin.extractEvents
React 事件系统中处理表单元素事件的核心逻辑,主要负责根据不同的表单元素类型和事件类型,选择合适的处理函数。
主要负责:
- 识别事件类型:根据目标元素类型和事件名称决定如何处理
- 生成合成事件:将原生 DOM 事件转换为 React 合成事件
- 处理特殊表单行为:如输入框值同步、焦点管理等
函数参数含义:
dispatchQueue
: 事件派发队列,存储待执行的事件监听器。domEventName
:原生 DOM 事件的名称。targetInst
:事件目标对应的 Fiber 实例。nativeEvent
:原生的 DOM 事件对象。nativeEventTarget
:原生事件的目标元素。eventSystemFlags
:事件系统的标志,用于表示事件的状态和特性。targetContainer
:事件发生的目标容器。
function extractEvents(
dispatchQueue: DispatchQueue,
domEventName: DOMEventName,
targetInst: null | Fiber, // 事件触发的 Fiber 节点。
nativeEvent: AnyNativeEvent,
nativeEventTarget: null | EventTarget,
eventSystemFlags: EventSystemFlags,
targetContainer: null | EventTarget,
) {
// 获取目标节点
// getNodeFromInstance:将 Fiber 节点转换为实际 DOM 节点。
const targetNode = targetInst ? getNodeFromInstance(targetInst) : window;
// getTargetInstFunc 事件处理函数
let getTargetInstFunc, handleEventFunc;
// 表单变更事件处理
// shouldUseChangeEvent:判断是否应使用 change 事件(如 select 元素、input[type=checkbox] 等)。
if (shouldUseChangeEvent(targetNode)) {
// 获取处理 change 事件的 Fiber 节点。
getTargetInstFunc = getTargetInstForChangeEvent;
// 文本输入元素处理
// isTextInputElement:判断是否为文本输入元素(如 input[type=text]、textarea)。
} else if (isTextInputElement(((targetNode: any): HTMLElement))) {
// isInputEventSupported:检测浏览器是否支持 input 事件
if (isInputEventSupported) {
getTargetInstFunc = getTargetInstForInputOrChangeEvent;
} else {
// getTargetInstForInputEventPolyfill:针对不支持 input 事件的浏览器,提供回退方案(如使用 keydown、paste 等事件模拟)。
getTargetInstFunc = getTargetInstForInputEventPolyfill;
handleEventFunc = handleEventsForInputEventPolyfill;
}
// 点击事件处理
// shouldUseClickEvent:判断是否应使用 click 事件(如 button、label 等元素)。
} else if (shouldUseClickEvent(targetNode)) {
getTargetInstFunc = getTargetInstForClickEvent;
// 自定义元素处理
// isCustomElement:判断是否为自定义元素(如 Web Component)。
} else if (
targetInst &&
isCustomElement(targetInst.elementType, targetInst.memoizedProps)
) {
getTargetInstFunc = getTargetInstForChangeEvent;
}
// 创建并累积事件
if (getTargetInstFunc) {
// 获取目标实例并创建事件
const inst = getTargetInstFunc(domEventName, targetInst);
if (inst) {
// 创建合成事件并添加到派发队列。
createAndAccumulateChangeEvent(
dispatchQueue,
inst,
nativeEvent,
nativeEventTarget,
);
return;
}
}
// 特殊事件处理(如输入事件填充)
if (handleEventFunc) {
// 处理特殊事件(如 input 事件的回退方案)。
handleEventFunc(domEventName, targetNode, targetInst);
}
// 焦点事件特殊处理
if (domEventName === 'focusout' && targetInst) {
const props = targetInst.memoizedProps;
// 处理受控输入元素的失焦事件,确保值的同步。
handleControlledInputBlur(((targetNode: any): HTMLInputElement), props);
}
}
事件处理类型
1、 表单变更事件处理 getTargetInstForChangeEvent
2、文本输入元素处理 getTargetInstForInputOrChangeEvent 、getTargetInstForInputEventPolyfill
3、点击事件处理 getTargetInstForClickEvent
4、自定义元素处理 getTargetInstForChangeEvent
工具函数1之 getTargetInstForChangeEvent(表单变更事件处理)
getTargetInstForChangeEvent
是 React 事件系统中用于 定位 change
事件目标 Fiber 节点 的工具函数。
function getTargetInstForChangeEvent(
domEventName: DOMEventName,
targetInst: null | Fiber,
) {
if (domEventName === 'change') {
return targetInst;
}
}
工具函数2之 getTargetInstForInputOrChangeEvent(文本输入元素处理)
React 事件系统中专门处理 输入 / 变更事件 的工具函数,主要用于判断目标节点的值是否发生变化,并返回对应的 Fiber 实例。
函数参数含义:
domEventName
: 原生 DOM 事件名称(如input
、change
)。targetInst
: 事件触发的目标 Fiber 节点。
function getTargetInstForInputOrChangeEvent(
domEventName: DOMEventName,
targetInst: null | Fiber,
) {
// 处理输入/变更事件
if (domEventName === 'input' || domEventName === 'change')
// getInstIfValueChanged 函数:检查目标节点的值是否与 React 内部维护的值不同。
// 如果不同,说明值发生了变化,返回该节点的 Fiber 实例。
// 如果相同,说明值未变化(可能是重复事件或虚假触发),返回 null。
return getInstIfValueChanged(targetInst);
}
}
工具函数之 getInstIfValueChanged
React 事件系统中用于检测表单元素值是否变化的核心工具,主要用于在处理输入事件时避免不必要的更新。
函数参数含义:
targetInst
: 事件触发的目标 Fiber 节点,包含组件的内部状态和属性。
function getInstIfValueChanged(targetInst: Object) {
// 从 Fiber 实例获取对应的 DOM 节点
const targetNode = getNodeFromInstance(targetInst);
// 检测值是否变更
if (updateValueIfChanged(((targetNode: any): HTMLInputElement))) {
return targetInst;
}
}
工具函数之 updateValueIfChanged
updateValueIfChanged
是 React 内部用于 检测并更新表单元素值 的核心函数,主要解决 受控组件与 DOM 状态同步 的问题。
function updateValueIfChanged(node: ElementWithValueTracker): boolean {
// 空节点
if (!node) {
return false;
}
// 获取值追踪器
const tracker = getTracker(node);
// 若不存在追踪器,认为已完成更新
if (!tracker) {
return true;
}
// 获取追踪器上一次值
const lastValue = tracker.getValue();
// 从 DOM 节点读取的当前值
const nextValue = getValueFromNode(node);
// 若值不同,则更新追踪器
if (nextValue !== lastValue) {
tracker.setValue(nextValue);
return true;
}
return false;
}
工具函数之 getTracker
getTracker
是 React 内部用于 获取 DOM 元素值追踪器 的工具函数。
function getTracker(node: ElementWithValueTracker) {
// _valueTracker 是 React 内部添加到 DOM 元素的私有属性
return node._valueTracker;
}
interface ElementWithValueTracker extends HTMLInputElement {
_valueTracker?: ?ValueTracker;
}
type ValueTracker = {
getValue(): string,
setValue(value: string): void,
stopTracking(): void,
};
工具函数之 getValueFromNode
React 事件系统中用于从 DOM 节点获取值的工具函数,主要用于处理不同类型的表单元素。
主要职责:
- 处理不同表单元素:根据元素类型(普通输入框、复选框 / 单选框等)获取对应的值。
- 标准化返回值:将各种表单元素的值统一转换为字符串类型。
- 处理空值情况:当节点不存在时返回空字符串,避免异常。
函数参数含义:
node
: DOM 节点(通常是表单元素,如<input>
、<textarea>
等)。
function getValueFromNode(node: HTMLInputElement): string {
let value = '';
if (!node) {
return value;
}
// isCheckable:判断元素是否为复选框或单选框(type="checkbox" 或 type="radio")。
if (isCheckable(node)) {
// 将布尔值(checked 属性)转换为字符串 "true" 或 "false"。
value = node.checked ? 'true' : 'false';
} else {
// 普通输入元素处理,获取 value 属性
value = node.value;
}
return value;
}
function isCheckable(elem: HTMLInputElement) {
const type = elem.type;
const nodeName = elem.nodeName;
return (
nodeName &&
nodeName.toLowerCase() === 'input' &&
(type === 'checkbox' || type === 'radio')
);
}
工具函数2之 getTargetInstForInputEventPolyfill( 文本输入元素处理)
React 事件系统中针对不支持 input
事件的浏览器提供的回退方案(Polyfill)。
函数参数含义:
domEventName
: 原生 DOM 事件名称(如selectionchange
、keyup
)。targetInst
: 事件触发的目标 Fiber 节点。
function getTargetInstForInputEventPolyfill(
domEventName: DOMEventName,
targetInst: null | Fiber,
) {
// 监听代替事件
if (
domEventName === 'selectionchange' ||
domEventName === 'keyup' ||
domEventName === 'keydown'
) {
// activeElementInst:当前聚焦元素的 Fiber 实例。
// getInstIfValueChanged:检查 DOM 值与 React 内部维护的值是否不同
return getInstIfValueChanged(activeElementInst);
}
}
工具函数之 handleEventsForInputEventPolyfill
React 事件系统中针对 不支持 input
事件的浏览器 提供的 输入事件监听回退方案。
主要作用是:
- 模拟
input
事件:在不支持input
事件的浏览器中,通过监听focusin
和focusout
事件来捕获输入变化。 - 启动 / 停止监听:在元素获得焦点时启动监听,失去焦点时停止监听。
- 轮询检测值变化:通过定期检查 DOM 值的变化来模拟实时输入事件。
函数参数含义:
domEventName
: 原生 DOM 事件名称(如focusin
、focusout
)。target
: 事件目标 DOM 节点(输入元素)。targetInst
: 事件目标 Fiber 节点。
function handleEventsForInputEventPolyfill(
domEventName: DOMEventName,
target: Instance | TextInstance,
targetInst: null | Fiber,
) {
// 焦点获得处理
if (domEventName === 'focusin') {
// stopWatchingForValueChange():停止之前的所有轮询监听(确保唯一性)。
stopWatchingForValueChange();
// startWatchingForValueChange(target, targetInst):启动对当前元素的值变化监听。
startWatchingForValueChange(target, targetInst);
// 焦点失去事件处理
} else if (domEventName === 'focusout') {
// stopWatchingForValueChange():停止当前元素的值变化监听,释放资源。
stopWatchingForValueChange();
}
}
工具函数之 stopWatchingForValueChange
React 事件系统中用于停止监听输入值变化的函数,主要针对不支持 input
事件的旧版浏览器(如 IE9 及以下)。
function stopWatchingForValueChange() {
// 如果没有活动元素,直接返回,避免不必要的操作。
if (!activeElement) {
return;
}
// detachEvent:IE 浏览器特有的方法,用于移除事件监听器。
// propertychange 事件:IE 特有的事件,当元素的属性值发生变化时触发。
// handlePropertyChange:事件处理函数,用于检测输入值的变化
(activeElement: any).detachEvent('onpropertychange', handlePropertyChange);
// 重置全局状态
activeElement = null;
activeElementInst = null;
}
工具函数之 startWatchingForValueChange
React 事件系统中用于监听输入值变化的函数,主要针对不支持 input
事件的旧版浏览器(如 IE9 及以下)。
function startWatchingForValueChange(
target: Instance | TextInstance,
targetInst: null | Fiber,
) {
// activeElement:全局变量,存储当前正在监听的 DOM 元素。
activeElement = target;
// activeElementInst:全局变量,存储对应的 Fiber 节点。
activeElementInst = targetInst;
// attachEvent:IE 浏览器特有的方法,用于添加事件监听器。
// propertychange 事件:IE 特有的事件,当元素的属性值发生变化时触发。
// handlePropertyChange:事件处理函数,用于检测输入值的变化。
(activeElement: any).attachEvent('onpropertychange', handlePropertyChange);
}
工具函数之 handlePropertyChange
React 事件系统中针对 IE 浏览器 propertychange
事件 的处理函数,主要用于在旧版浏览器中模拟 input
事件的行为。
handlePropertyChange
的主要作用是:
- 过滤属性变化:只处理
value
属性的变化,忽略其他属性(如style
、className
)。 - 检测值变化:通过
getInstIfValueChanged
确认值确实发生了变化。 - 手动触发变更事件:如果值变化,调用
manualDispatchChangeEvent
模拟change
事件。
function handlePropertyChange(nativeEvent) {
// 只关注 value 属性的变化,其他属性变化直接忽略。
if (nativeEvent.propertyName !== 'value') {
return;
}
// getInstIfValueChanged:检查 DOM 值与 React 内部维护的值是否不同。
if (getInstIfValueChanged(activeElementInst)) {
// manualDispatchChangeEvent:手动构建并派发 change 事件。
// 创建合成事件对象。
// 收集事件传播路径上的所有监听器。
// 按顺序执行监听器(捕获阶段 → 目标 → 冒泡阶段)。
manualDispatchChangeEvent(nativeEvent);
}
}
工具函数之 manualDispatchChangeEvent
React 事件系统中用于手动触发变更事件的核心函数,主要用于在不支持现代事件 API 的浏览器中模拟 change
事件。
function manualDispatchChangeEvent(nativeEvent: AnyNativeEvent) {
// 创建派发队列
const dispatchQueue: DispatchQueue = [];
// 收集变更事件监听器
createAndAccumulateChangeEvent(
dispatchQueue,
activeElementInst,
nativeEvent,
getEventTarget(nativeEvent),
);
// 批量执行事件处理
batchedUpdates(runEventInBatch, dispatchQueue);
}
React 事件系统中用于执行事件派发队列的核心函数,主要负责按顺序处理事件监听器。
function runEventInBatch(dispatchQueue: DispatchQueue) {
// processDispatchQueue:处理事件派发队列的核心函数。
// 参数 1:事件派发队列(DispatchQueue),包含事件对象和监听器数组。
// 参数 2:起始索引(0 表示从队列头部开始处理)。
// 这个函数是 React 事件派发流程的入口点
processDispatchQueue(dispatchQueue, 0);
}
工具函数3之 getTargetInstForClickEvent(点击事件处理)
React 事件系统中专门处理 点击事件(click) 的工具函数,主要用于检测点击操作是否导致表单元素的值发生变化。
函数参数含义:
domEventName
: 原生 DOM 事件名称(如click
)。targetInst
: 事件触发的目标 Fiber 节点。
function getTargetInstForClickEvent(
domEventName: DOMEventName,
targetInst: null | Fiber,
) {
// 只处理 click 事件
if (domEventName === 'click') {
// getInstIfValueChanged:检查目标元素的 DOM 值与 React 内部维护的值是否不同。
return getInstIfValueChanged(targetInst);
}
}
createAndAccumulateChangeEvent
React 事件系统中处理 表单变更事件(onChange) 的核心逻辑,主要负责创建合成事件并将其加入派发队列。
主要负责:
- 标记状态恢复需求:确保事件处理后恢复焦点或滚动位置
- 收集事件监听器:从 Fiber 节点向上查找所有注册的
onChange
回调 - 创建合成事件:将原生 DOM 事件转换为 React 合成事件
- 加入派发队列:将事件和监听器组合加入队列等待处理
函数参数含义:
dispatchQueue
: 事件派发队列,存储待执行的事件和监听器。inst
: 事件触发的目标 Fiber 节点。nativeEvent
: 原生 DOM 事件对象(如MouseEvent
、KeyboardEvent
)。target
: 事件目标 DOM 节点。
function createAndAccumulateChangeEvent(
dispatchQueue: DispatchQueue,
inst: null | Fiber,
nativeEvent: AnyNativeEvent,
target: null | EventTarget,
) {
// Flag this event loop as needing state restore.
// enqueueStateRestore:将目标 DOM 节点加入状态恢复队列。
enqueueStateRestore(((target: any): Node));
// 收集事件监听器
const listeners = accumulateTwoPhaseListeners(inst, 'onChange');
if (listeners.length > 0) {
// 创建合成事件
const event: ReactSyntheticEvent = new SyntheticEvent(
'onChange', // React 事件名称
'change', // 原生事件名称
null, // 无宿主组件(由 React 内部处理)
nativeEvent, // 原生 DOM 事件对象
target, // 事件目标 DOM 节点
);
// 加入派发队列
dispatchQueue.push({event, listeners});
}
}
工具函数之 enqueueStateRestore
enqueueStateRestore
是 React 内部用于 管理状态恢复队列 的核心函数,主要解决 异步渲染过程中状态恢复顺序 的问题。
// 当前正在处理的目标节点(恢复焦点 / 滚动位置等状态)
let restoreTarget = null;
// 等待处理的目标节点队列(FIFO 队列)
let restoreQueue = null;
function enqueueStateRestore(target: Node): void {
if (restoreTarget) {
if (restoreQueue) {
restoreQueue.push(target);
} else {
restoreQueue = [target];
}
} else {
restoreTarget = target;
}
}
工具函数之 isCustomElement
isCustomElement
是 React 内部用于 判断元素是否为自定义元素(Web Component) 的工具函数。
function isCustomElement(tagName: string, props: Object): boolean {
// 根据 Web Components 标准,自定义元素标签名必须包含连字符(-)
if (tagName.indexOf('-') === -1) {
return false;
}
switch (tagName) {
// 黑名单过滤
case 'annotation-xml':
case 'color-profile':
case 'font-face':
case 'font-face-src':
case 'font-face-uri':
case 'font-face-format':
case 'font-face-name':
case 'missing-glyph':
return false;
default:
return true;
}
}
工具函数之 accumulateTwoPhaseListeners
React 事件系统中实现 事件捕获和冒泡机制 的核心函数,主要负责收集事件传播路径上的所有监听器。
函数参数含义:
targetFiber
: 事件触发的目标 Fiber 节点。reactName
: React 事件名称(如onClick
)。
function accumulateTwoPhaseListeners(
targetFiber: Fiber | null,
reactName: string,
): Array<DispatchListener> {
// 生成捕获阶段事件名:例如,将 onClick 转换为 onClickCapture。
const captureName = reactName + 'Capture';
const listeners: Array<DispatchListener> = [];
let instance = targetFiber;
// Accumulate all instances and listeners via the target -> root path.
// 从目标节点向上遍历至根节点(instance.return 指向父 Fiber 节点)。
while (instance !== null) {
const {stateNode, tag} = instance;
// Handle listeners that are on HostComponents (i.e. <div>)
if (
(tag === HostComponent ||
tag === HostHoistable ||
tag === HostSingleton) &&
stateNode !== null
) {
const currentTarget = stateNode;
const captureListener = getListener(instance, captureName);
if (captureListener != null) {
listeners.unshift(
createDispatchListener(instance, captureListener, currentTarget),
);
}
const bubbleListener = getListener(instance, reactName);
if (bubbleListener != null) {
listeners.push(
createDispatchListener(instance, bubbleListener, currentTarget),
);
}
}
instance = instance.return;
}
return listeners;
}
4、SelectEventPlugin.extractEvents
extractEvents
是 React 内部用于 事件提取与分发 的核心函数,主要解决 将原生 DOM 事件转换为 React 合成事件 的问题。
主要作用:
- 事件类型分发:根据原生事件名称(如
focusin
、mousedown
)执行不同的处理逻辑。 - 状态管理:维护全局状态(如鼠标按下状态、活动元素)。
- 事件构造:针对特定事件类型(如选区变化、键盘事件)构造合成事件。
函数参数含义:
dispatchQueue
: 事件派发队列,存储待执行的事件监听器。domEventName
:原生 DOM 事件的名称。targetInst
:事件目标对应的 Fiber 实例。nativeEvent
:原生的 DOM 事件对象。nativeEventTarget
:原生事件的目标元素。eventSystemFlags
:事件系统的标志,用于表示事件的状态和特性。targetContainer
:事件发生的目标容器。
function extractEvents(
dispatchQueue: DispatchQueue,
domEventName: DOMEventName,
targetInst: null | Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: null | EventTarget,
eventSystemFlags: EventSystemFlags,
targetContainer: EventTarget,
) {
// 获取目标节点
// getNodeFromInstance:将 Fiber 节点转换为实际 DOM 元素。
const targetNode = targetInst ? getNodeFromInstance(targetInst) : window;
switch (domEventName) {
/** 焦点事件处理 */
// focusin:记录当前聚焦的输入元素或可编辑元素。
case 'focusin':
if (
isTextInputElement((targetNode: any)) ||
targetNode.contentEditable === 'true'
) {
activeElement = targetNode;
activeElementInst = targetInst;
lastSelection = null;
}
break;
// focusout:清除聚焦状态,释放资源。
case 'focusout':
activeElement = null;
activeElementInst = null;
lastSelection = null;
break;
/** 鼠标事件处理 */
// mousedown:标记鼠标按下状态。
case 'mousedown':
mouseDown = true;
break;
// 处理右键菜单、鼠标抬起、拖拽释放
case 'contextmenu':
case 'mouseup':
case 'dragend':
// 标记鼠标释放
mouseDown = false;
// 构造选区事件(处理文本选择)。
constructSelectEvent(dispatchQueue, nativeEvent, nativeEventTarget);
break;
/** 选区变化事件处理 */
case 'selectionchange':
if (skipSelectionChangeEvent) {
break;
}
/** 键盘事件处理 */
case 'keydown':
case 'keyup':
// 构造选区事件,处理键盘操作导致的文本选择变化。
constructSelectEvent(dispatchQueue, nativeEvent, nativeEventTarget);
}
}
let activeElement = null; // 记录当前聚焦的 文本输入元素
let activeElementInst = null; // 记录当前聚焦元素对应的 Fiber 实例
let lastSelection = null; // 缓存 上一次记录的选区信息
let mouseDown = false; // 鼠标是否处于按下状态
工具函数之 skipSelectionChangeEvent
skipSelectionChangeEvent
是 React 内部用于 检测是否需要跳过 selectionchange
事件监听 的布尔标志。
const skipSelectionChangeEvent =
canUseDOM && 'documentMode' in document && document.documentMode <= 11;
// canUseDOM:判断是否在浏览器环境中运行
// 'documentMode' in document:检测是否为 IE 浏览器(documentMode 是 IE 特有的属性)
// document.documentMode <= 11:判断是否为 IE 11 或更低版本
const canUseDOM: boolean = !!(
typeof window !== 'undefined' &&
typeof window.document !== 'undefined' &&
typeof window.document.createElement !== 'undefined'
);
工具函数之 constructSelectEvent
constructSelectEvent
是 React 内部用于 处理文本选择事件 的核心函数,主要解决 在文本选区变化时触发 onSelect
事件 的问题。
函数参数含义:
dispatchQueue
: 事件派发队列,存储待执行的事件监听器。nativeEvent
:原生的 DOM 事件对象。nativeEventTarget
:原生事件的目标元素。
function constructSelectEvent(
dispatchQueue: DispatchQueue,
nativeEvent: AnyNativeEvent,
nativeEventTarget: null | EventTarget,
) {
// 获取document对象
const doc = getEventTargetDocument(nativeEventTarget);
// 只在当前聚焦元素上处理选区变化
if (
mouseDown ||
activeElement == null ||
activeElement !== getActiveElement(doc)
) {
return;
}
// 使用 getSelection 获取当前选区
const currentSelection = getSelection(activeElement);
// 与上次保存的选区(lastSelection)比较
if (!lastSelection || !shallowEqual(lastSelection, currentSelection)) {
lastSelection = currentSelection;
// 收集 onSelect 事件的监听函数
const listeners = accumulateTwoPhaseListeners(
activeElementInst,
'onSelect',
);
if (listeners.length > 0) {
// 创建合成事件
const event: ReactSyntheticEvent = new SyntheticEvent(
'onSelect',
'select',
null,
nativeEvent,
nativeEventTarget,
);
// 加入派发队列
dispatchQueue.push({event, listeners});
// 设置事件目标为当前活动元素
event.target = activeElement;
}
}
}
工具函数之 getEventTargetDocument
getEventTargetDocument
是 React 内部用于 获取事件目标所属文档对象 的工具函数。
function getEventTargetDocument(eventTarget: any) {
// 浏览器环境中,window 对象的 window 属性指向自身
return eventTarget.window === eventTarget
? eventTarget.document
: eventTarget.nodeType === DOCUMENT_NODE
// 若是 Document 对象,则直接返回自身
? eventTarget
// 返回节点所属的文档对象(ownerDocument)
: eventTarget.ownerDocument;
}
工具函数之 getActiveElement
React 中用于获取当前活动元素的工具函数,主要处理不同浏览器环境下的兼容性问题。
function getActiveElement(doc: ?Document): ?Element {
// 获取文档对象
doc = doc || (typeof document !== 'undefined' ? document : undefined);
if (typeof doc === 'undefined') {
return null;
}
try {
// activeElement:标准属性,返回当前获得焦点的元素。
return doc.activeElement || doc.body;
} catch (e) {
// 异常处理:某些浏览器(如旧版 IE)在某些情况下可能抛出错误,此时回退到 doc.body。
return doc.body;
}
}
工具函数之 getSelection
getSelection
函数用于 获取 DOM 元素中的文本选择范围,根据元素类型智能选择不同的获取方式:
- 对于输入框类元素(如
<input>
、<textarea>
),使用selectionStart/End
属性 - 对于其他元素(如
contentEditable
元素),使用window.getSelection()
API
function getSelection(node: any) {
if ('selectionStart' in node && hasSelectionCapabilities(node)) {
return {
// HTMLTextAreaElement,HTMLInputElement等
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLTextAreaElement/selectionStart
start: node.selectionStart, // 选中文本的起始位置
end: node.selectionEnd, // 选中文本的结束位置
};
// 通用dom元素
} else {
const win =
(node.ownerDocument && node.ownerDocument.defaultView) || window;
// https://developer.mozilla.org/zh-CN/docs/Web/API/Window/getSelection
const selection = win.getSelection();
return {
anchorNode: selection.anchorNode, // 选区起始节点
anchorOffset: selection.anchorOffset, // 起始节点内的偏移量
focusNode: selection.focusNode, // 选区结束节点
focusOffset: selection.focusOffset, // 结束节点内的偏移量
};
}
}
工具函数之 hasSelectionCapabilities
hasSelectionCapabilities
是一个用于 判断 DOM 元素是否支持文本选择和操作 的工具函数。它通过检查元素类型和属性,确定该元素是否允许用户选择文本、获取选中文本范围或执行文本编辑操作。
function hasSelectionCapabilities(elem) {
const nodeName = elem && elem.nodeName && elem.nodeName.toLowerCase();
return (
nodeName &&
((nodeName === 'input' &&
(elem.type === 'text' ||
elem.type === 'search' ||
elem.type === 'tel' ||
elem.type === 'url' ||
elem.type === 'password')) ||
nodeName === 'textarea' ||
elem.contentEditable === 'true')
);
}
工具函数之 shallowEqual
shallowEqual
是 React 内部用于 浅比较两个对象是否相等 的工具函数
function shallowEqual(objA: mixed, objB: mixed): boolean {
if (is(objA, objB)) {
return true;
}
// 排除非对象类型(如函数、日期等)
if (
typeof objA !== 'object' ||
objA === null ||
typeof objB !== 'object' ||
objB === null
) {
return false;
}
// 属性比较
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
for (let i = 0; i < keysA.length; i++) {
const currentKey = keysA[i];
if (
!hasOwnProperty.call(objB, currentKey) ||
!is(objA[currentKey], objB[currentKey])
) {
return false;
}
}
return true;
}
工具函数之 objectIs
用于 判断两个值是否为同一个值。与 ===
相比,它修复了两个特殊情况:+0
与 -0
的区分,以及 NaN
的比较。
function is(x: any, y: any) {
return (
(x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y)
);
}
const objectIs: (x: any, y: any) => boolean =
typeof Object.is === 'function' ? Object.is : is;
// Object.is() 静态方法确定两个值是否为相同值。
5、BeforeInputEventPlugin.extractEvents
extractEvents
是 React 事件系统中用于 从原生事件提取并分类处理合成事件 的核心函数,主要负责 将单一原生事件映射到多个合成事件类型。
函数参数含义:
dispatchQueue
: 事件派发队列,存储待执行的事件监听器。domEventName
:原生 DOM 事件的名称。targetInst
:事件目标对应的 Fiber 实例。nativeEvent
:原生的 DOM 事件对象。nativeEventTarget
:原生事件的目标元素。eventSystemFlags
:事件系统的标志,用于表示事件的状态和特性。targetContainer
:事件发生的目标容器。
function extractEvents(
dispatchQueue: DispatchQueue,
domEventName: DOMEventName,
targetInst: null | Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: null | EventTarget,
eventSystemFlags: EventSystemFlags,
targetContainer: EventTarget,
): void {
// 处理输入法组合文本事件
extractCompositionEvent(
dispatchQueue,
domEventName,
targetInst,
nativeEvent,
nativeEventTarget,
);
// 处理输入前状态事件
extractBeforeInputEvent(
dispatchQueue,
domEventName,
targetInst,
nativeEvent,
nativeEventTarget,
);
}
extractCompositionEvent
extractCompositionEvent
是 React 内部用于 处理输入法组合文本事件 的核心函数,主要解决 非直接输入场景下文本内容的捕获与处理 问题。
函数参数含义:
dispatchQueue
: 事件派发队列,存储待执行的事件监听器。domEventName
:原生 DOM 事件的名称。targetInst
:事件目标对应的 Fiber 实例。nativeEvent
:原生的 DOM 事件对象。nativeEventTarget
:原生事件的目标元素。
function extractCompositionEvent(
dispatchQueue: DispatchQueue,
domEventName: DOMEventName,
targetInst: null | Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: null | EventTarget,
) {
let eventType;
let fallbackData;
// 原生支持:如果浏览器支持 composition 系列事件(如 compositionstart),直接使用。
if (canUseCompositionEvent) {
// 获取合成事件名称
eventType = getCompositionEventType(domEventName);
// 回退机制:在不支持的浏览器中,通过 keydown、keyup 等事件模拟输入法开始 / 结束。
// isComposing:全局状态,标记当前是否处于输入法组合状态。
} else if (!isComposing) {
//isFallbackCompositionStart判断是否 不支持 compositionstart 事件的浏览器中模拟输入法开始
if (isFallbackCompositionStart(domEventName, nativeEvent)) {
eventType = 'onCompositionStart';
}
//isFallbackCompositionEnd判断是否 在不支持 compositionend 事件的浏览器中模拟输入法结束
} else if (isFallbackCompositionEnd(domEventName, nativeEvent)) {
eventType = 'onCompositionEnd';
}
if (!eventType) {
return null;
}
// 启动回退机制 和 非韩语输入法处理
if (useFallbackCompositionData && !isUsingKoreanIME(nativeEvent)) {
// 当前不在组合状态(!isComposing)
if (!isComposing && eventType === 'onCompositionStart') {
// FallbackCompositionStateInitialize:初始化输入法组合状态,开始记录输入内容。
isComposing = FallbackCompositionStateInitialize(nativeEventTarget);
} else if (eventType === 'onCompositionEnd') {
if (isComposing) {
// FallbackCompositionStateGetData:获取输入法组合期间的文本内容(如拼音输入过程中的字符)。
fallbackData = FallbackCompositionStateGetData();
}
}
}
// 收集合成事件监听器
const listeners = accumulateTwoPhaseListeners(targetInst, eventType);
if (listeners.length > 0) {
// 创建合成事件
const event: ReactSyntheticEvent = new SyntheticCompositionEvent(
eventType,
domEventName,
null,
nativeEvent,
nativeEventTarget,
);
// 加入派发队列
dispatchQueue.push({event, listeners});
// 由于韩语输入法的特殊性,需要通过回退机制获取完整的输入内容。
if (fallbackData) {
event.data = fallbackData;
} else {
// 自定义事件数据:从原生事件中提取输入法内容(如拼音、日文假名等)。
// 获取event.detail.data
const customData = getDataFromCustomEvent(nativeEvent);
if (customData !== null) {
event.data = customData;
}
}
}
}
工具函数之 getCompositionEventType
getCompositionEventType
是 React 内部用于 将原生输入法事件映射到对应合成事件名称 的工具函数,主要解决 浏览器原生输入法事件(如 compositionstart
)与 React 合成事件(如 onCompositionStart
) 之间的命名映射问题。
function getCompositionEventType(domEventName: DOMEventName) {
switch (domEventName) {
case 'compositionstart':
return 'onCompositionStart';
case 'compositionend':
return 'onCompositionEnd';
case 'compositionupdate':
return 'onCompositionUpdate';
}
}
工具函数之 isFallbackCompositionStart
isFallbackCompositionStart
是 React 内部用于 在不支持 compositionstart
事件的浏览器中模拟输入法开始 的检测函数。
function isFallbackCompositionStart(
domEventName: DOMEventName,
nativeEvent: any,
): boolean {
// 仅处理键盘按下事件
return domEventName === 'keydown' && nativeEvent.keyCode === START_KEYCODE;
}
const START_KEYCODE = 229;
工具函数之 isFallbackCompositionEnd
isFallbackCompositionEnd
是 React 内部用于 在不支持 compositionend
事件的浏览器中模拟输入法结束 的检测函数。
function isFallbackCompositionEnd(
domEventName: DOMEventName,
nativeEvent: any,
): boolean {
switch (domEventName) {
case 'keyup':
// 处理按键释放事件
// Command keys insert or clear IME input.
return END_KEYCODES.indexOf(nativeEvent.keyCode) !== -1;
case 'keydown':
// 处理按键按下事件
// Expect IME keyCode on each keydown. If we get any other
// code we must have exited earlier.
return nativeEvent.keyCode !== START_KEYCODE;
case 'keypress':
case 'mousedown':
case 'focusout':
// 处理其他可能导致输入法结束的事件
// Events are not possible without cancelling IME.
return true;
default:
return false;
}
}
function isUsingKoreanIME(nativeEvent: any) {
return nativeEvent.locale === 'ko';
}
工具函数之 FallbackCompositionStateInitialize
initialize初始化函数,用于 记录元素的初始状态。
// 目标 DOM 元素
let root = null;
// 初始化时的文本内容
let startText = null;
// 备选文本内容(用于回滚)
let fallbackText = null;
function initialize(nativeEventTarget) {
// 设置目标元素
root = nativeEventTarget;
// 保存初始文本
startText = getText();
return true;
}
工具函数之 getText
getText
函数用于 根据元素类型获取其文本内容。其核心逻辑是:
- 优先检查元素是否有
value
属性(如输入框、文本域) - 若无
value
属性,则获取textContent
(如普通 DOM 元素)
let root = null;
function getText() {
if ('value' in root) {
return root.value;
}
return root.textContent;
}
工具函数之 FallbackCompositionStateGetData
getData
函数实现了一个 文本差异分析算法,用于提取两次状态间的新增文本。
function getData() {
if (fallbackText) {
return fallbackText;
}
let start;
const startValue = startText;
const startLength = startValue.length;
let end;
const endValue = getText();
const endLength = endValue.length;
// 从前向后找差异起始点:
for (start = 0; start < startLength; start++) {
if (startValue[start] !== endValue[start]) {
break;
}
}
// 从后向前找差异结束点:
const minEnd = startLength - start;
for (end = 1; end <= minEnd; end++) {
if (startValue[startLength - end] !== endValue[endLength - end]) {
break;
}
}
// 计算切片参数:
const sliceTail = end > 1 ? 1 - end : undefined;
fallbackText = endValue.slice(start, sliceTail);
return fallbackText;
}
工具函数之 getDataFromCustomEvent
getDataFromCustomEvent
是 React 内部用于 从自定义事件中提取数据 的工具函数,主要解决 DOM 自定义事件(CustomEvent)与 React 合成事件 之间的数据传递问题。
function getDataFromCustomEvent(nativeEvent: any) {
// CustomEvent 接口的 detail 只读属性返回初始化事件时传递的任何数据。
const detail = nativeEvent.detail;
if (typeof detail === 'object' && 'data' in detail) {
return detail.data;
}
return null;
}
extractBeforeInputEvent
React 事件系统中处理 beforeinput
事件的核心逻辑,主要负责捕获用户输入的字符并创建合成事件。
function extractBeforeInputEvent(
dispatchQueue: DispatchQueue,
domEventName: DOMEventName,
targetInst: null | Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: null | EventTarget,
) {
let chars;
// 原生支持:如果浏览器支持 beforeinput 或 textinput 事件,直接从中提取字符。
if (canUseTextInputEvent) {
chars = getNativeBeforeInputChars(domEventName, nativeEvent);
} else {
// 回退机制:在不支持的浏览器中,通过分析 keydown、paste 等事件模拟输入字符。
chars = getFallbackBeforeInputChars(domEventName, nativeEvent);
}
if (!chars) {
return null;
}
// 收集监听器:通过 accumulateTwoPhaseListeners 收集所有注册的 onBeforeInput 监听器。
const listeners = accumulateTwoPhaseListeners(targetInst, 'onBeforeInput');
if (listeners.length > 0) {
// 创建合成事件:将原生事件包装为 SyntheticInputEvent
const event: ReactSyntheticEvent = new SyntheticInputEvent(
'onBeforeInput',
'beforeinput',
null,
nativeEvent,
nativeEventTarget,
);
// 加入派发队列:将事件和监听器组合加入队列
dispatchQueue.push({event, listeners});
// 设置 data 属性为输入字符
event.data = chars;
}
}
const canUseTextInputEvent = canUseDOM && 'TextEvent' in window && !documentMode;
工具函数之 getNativeBeforeInputChars
getNativeBeforeInputChars
是 React 内部用于 从原生输入事件中提取即将插入的字符 的工具函数。
function getNativeBeforeInputChars(
domEventName: DOMEventName,
nativeEvent: any,
): ?string {
switch (domEventName) {
// 输入法组合文本结束
case 'compositionend':
return getDataFromCustomEvent(nativeEvent);
// 键盘按下(已弃用,但仍需兼容)
case 'keypress':
// UIEvent 接口的 UIEvent.which 只读属性返回一个数字,表示按下了鼠标上的哪个按钮,或者是键盘上按下的键的 keyCode 或字符代码(charCode)的数字值。
const which = nativeEvent.which;
// SPACEBAR_CODE = 32;
if (which !== SPACEBAR_CODE) {
return null;
}
hasSpaceKeypress = true;
return SPACEBAR_CHAR;
// 文本输入(较新的 API)
case 'textInput':
// 空格键可能同时触发 keypress 和 textInput 事件
// Record the characters to be added to the DOM.
const chars = nativeEvent.data;
if (chars === SPACEBAR_CHAR && hasSpaceKeypress) {
return null;
}
return chars;
// 其他事件(如 click、scroll)返回 null
default:
// For other native event types, do nothing.
return null;
}
}
工具函数之 getFallbackBeforeInputChars
React 在不支持 beforeinput
事件的浏览器中,用于模拟获取输入字符的回退方案。
function getFallbackBeforeInputChars(
domEventName: DOMEventName,
nativeEvent: any,
): ?string {
// isComposing:标记当前是否处于输入法组合状态(如拼音输入过程)。
if (isComposing) {
if (
domEventName === 'compositionend' ||
(!canUseCompositionEvent &&
// 兼容性处理:在不支持 composition 事件的浏览器中,通过 isFallbackCompositionEnd 模拟检测组合结束。
isFallbackCompositionEnd(domEventName, nativeEvent))
) {
// 组合结束:当输入法组合结束时(compositionend 事件),获取并返回组合后的文本
const chars = FallbackCompositionStateGetData();
// 重置
FallbackCompositionStateReset();
isComposing = false;
return chars;
}
return null;
}
switch (domEventName) {
case 'paste':
// 粘贴操作由专门的事件处理
return null;
case 'keypress':
// 过滤快捷键命令(如 Ctrl+C、Alt+Tab)。
if (!isKeypressCommand(nativeEvent)) {
// 从 nativeEvent.char 或 nativeEvent.which 获取输入字符。
if (nativeEvent.char && nativeEvent.char.length > 1) {
return nativeEvent.char;
} else if (nativeEvent.which) {
return String.fromCharCode(nativeEvent.which);
}
}
return null;
case 'compositionend':
// 处理韩语输入法等特殊场景。
return useFallbackCompositionData && !isUsingKoreanIME(nativeEvent)
? null
// 直接从 nativeEvent.data 获取组合后的文本。
: nativeEvent.data;
default:
return null;
}
}
function reset() {
root = null;
startText = null;
fallbackText = null;
}
function isKeypressCommand(nativeEvent: any) {
return (
(nativeEvent.ctrlKey || nativeEvent.altKey || nativeEvent.metaKey) &&
// ctrlKey && altKey is equivalent to AltGr, and is not a command.
!(nativeEvent.ctrlKey && nativeEvent.altKey)
);
}
6、FormActionEventPlugin.extractEvents
extractEvents
是 React 内部用于 处理表单提交事件 的核心函数,主要解决 原生表单提交行为与 React 合成事件系统的集成 问题。
函数参数含义:
dispatchQueue
: 事件派发队列,存储待执行的事件监听器。domEventName
:原生 DOM 事件的名称。targetInst
:事件目标对应的 Fiber 实例。nativeEvent
:原生的 DOM 事件对象。nativeEventTarget
:原生事件的目标元素。eventSystemFlags
:事件系统的标志,用于表示事件的状态和特性。targetContainer
:事件发生的目标容器。
function extractEvents(
dispatchQueue: DispatchQueue,
domEventName: DOMEventName,
maybeTargetInst: null | Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: null | EventTarget,
eventSystemFlags: EventSystemFlags,
targetContainer: EventTarget,
) {
// 仅处理表单提交事件:忽略其他类型事件(如 click、input 等)
if (domEventName !== 'submit') {
return;
}
// maybeTargetInst:React 组件实例(Fiber)
// nativeEventTarget:DOM 中的表单元素
if (!maybeTargetInst || maybeTargetInst.stateNode !== nativeEventTarget) {
return;
}
const formInst = maybeTargetInst;
const form: HTMLFormElement = (nativeEventTarget: any);
// 提取表单 action 属性
let action = coerceFormActionProp(
(getFiberCurrentPropsFromNode(form): any).action,
);
// 在 SubmitEvent 接口的只读属性 submitter 用于指定提交按钮或者被用来提交表单的其他元素。
let submitter: null | void | HTMLInputElement | HTMLButtonElement =
(nativeEvent: any).submitter;
// 处理 submitter 的 formAction 属性
let submitterAction;
if (submitter) {
const submitterProps = getFiberCurrentPropsFromNode(submitter);
submitterAction = submitterProps
? coerceFormActionProp((submitterProps: any).formAction)
:
((submitter.getAttribute('formAction'): any): string | null);
if (submitterAction !== null) {
action = submitterAction;
submitter = null;
}
}
// 创建合成事件
const event = new SyntheticEvent(
'action', // 事件名称
'action',
null,
nativeEvent, // 原生 DOM 事件
nativeEventTarget, // 事件目标 DOM 元素
);
function submitForm(){
// 省略代码
}
// 事件分发队列
dispatchQueue.push({
event,
listeners: [
{
instance: null,
listener: submitForm,
currentTarget: form,
},
],
});
}
function submitForm() {
// nativeEvent.defaultPrevented:检查是否已调用 event.preventDefault()
if (nativeEvent.defaultPrevented) {
// didCurrentEventScheduleTransition():检查当前事件是否安排了过渡动画。
if (didCurrentEventScheduleTransition()) {
// 创建表单数据
const formData = submitter
? createFormDataWithSubmitter(form, submitter)
: new FormData(form);
const pendingState: FormStatus = {
pending: true,
data: formData,
method: form.method,
action: action,
};
// 启动过渡动画并提交表单
startHostTransition(
formInst,
pendingState,
null,
formData,
);
}
// action 为函数:表示用户提供了自定义表单处理函数。
} else if (typeof action === 'function') {
// 阻止原生提交
event.preventDefault();
// 创建表单数据
const formData = submitter
? createFormDataWithSubmitter(form, submitter)
: new FormData(form);
// 设置表单状态为 pending
const pendingState: FormStatus = {
pending: true,
data: formData,
method: form.method,
action: action,
};
// 启动过渡并执行自定义 action
startHostTransition(formInst, pendingState, action, formData);
} else {
}
}
工具函数之 coerceFormActionProp
coerceFormActionProp
是 React 内部用于 规范化表单 action
属性值 的工具函数,主要解决 不同类型输入值到合法表单提交目标的转换 问题。
function coerceFormActionProp(
actionProp: mixed,
): string | (FormData => void | Promise<void>) | null {
// This should match the logic in ReactDOMComponent
if (
actionProp == null ||
typeof actionProp === 'symbol' ||
typeof actionProp === 'boolean'
) {
// null/undefined/symbol/boolean → null
return null;
} else if (typeof actionProp === 'function') {
return (actionProp: any);
} else {
// string → 经过 sanitizeURL 处理的字符串
return (sanitizeURL(
enableTrustedTypesIntegration ? actionProp : '' + (actionProp: any),
): any);
}
}
const enableTrustedTypesIntegration = false;
工具函数之 didCurrentEventScheduleTransition
React 事件系统中用于检测当前事件是否安排了过渡动画的核心逻辑,主要通过 currentEventTransitionLane
全局变量来跟踪过渡状态。
let currentEventTransitionLane: Lane = NoLane;
function didCurrentEventScheduleTransition(): boolean {
// 如果 currentEventTransitionLane 不为 NoLane,说明当前事件中安排了过渡任务。
return currentEventTransitionLane !== NoLane;
}
工具函数之 createFormDataWithSubmitter
createFormDataWithSubmitter
是 React 内部用于 创建包含提交按钮数据的 FormData 对象 的工具函数。
function createFormDataWithSubmitter(
form: HTMLFormElement,
submitter: HTMLInputElement | HTMLButtonElement,
) {
const temp = submitter.ownerDocument.createElement('input');
temp.name = submitter.name;
temp.value = submitter.value;
// 当表单有 id 且提交按钮通过 form 属性关联到该表单时
if (form.id) {
temp.setAttribute('form', form.id);
}
(submitter.parentNode: any).insertBefore(temp, submitter);
// 创建表单数据
const formData = new FormData(form);
// 移除临时创建的input
(temp.parentNode: any).removeChild(temp);
return formData;
}