Appearance
合成事件系统原理 - React事件机制深度解析
1. 合成事件概述
1.1 什么是合成事件
typescript
const syntheticEventConcept = {
definition: 'React对原生DOM事件的跨浏览器包装器',
特点: {
跨浏览器: '抹平浏览器差异',
性能优化: '事件委托到根节点',
统一接口: '一致的事件API',
池化管理: '事件对象复用(React 17前)'
},
与原生事件的区别: {
命名: 'onClick vs onclick (驼峰命名)',
阻止默认: 'e.preventDefault() (不能return false)',
this绑定: '需要手动绑定或使用箭头函数',
事件委托: '委托到根节点而非目标元素',
对象池: '事件对象会被复用(17前)'
}
};1.2 为什么需要合成事件
typescript
const whySyntheticEvents = {
跨浏览器兼容: {
问题: '不同浏览器事件API差异',
example: `
// IE: attachEvent
element.attachEvent('onclick', handler);
// 标准: addEventListener
element.addEventListener('click', handler);
// React: 统一接口
<div onClick={handler} />
`,
解决: 'React统一处理兼容性'
},
性能优化: {
问题: '每个元素绑定事件占用内存',
example: `
// 原生: 1000个元素1000个监听器
items.forEach(item => {
item.addEventListener('click', handler);
});
// React: 只在根节点一个监听器
<div>{items.map(item => <Item onClick={handler} />)}</div>
`,
解决: '事件委托到根节点'
},
事件池: {
问题: '频繁创建事件对象消耗性能',
solution: '复用事件对象(17前)',
注意: 'React 17+废弃了事件池'
},
统一管理: {
benefit: [
'事件优先级控制',
'批量更新',
'事件重放',
'错误处理'
]
}
};1.3 合成事件类型体系
typescript
// SyntheticEvent基类
interface BaseSyntheticEvent<E = object, C = any, T = any> {
nativeEvent: E;
currentTarget: C;
target: T;
bubbles: boolean;
cancelable: boolean;
defaultPrevented: boolean;
eventPhase: number;
isTrusted: boolean;
preventDefault(): void;
isDefaultPrevented(): boolean;
stopPropagation(): void;
isPropagationStopped(): boolean;
persist(): void;
timeStamp: number;
type: string;
}
// 具体事件类型
interface SyntheticMouseEvent<T = Element> extends BaseSyntheticEvent<MouseEvent, EventTarget & T, EventTarget> {
altKey: boolean;
button: number;
buttons: number;
clientX: number;
clientY: number;
ctrlKey: boolean;
metaKey: boolean;
pageX: number;
pageY: number;
relatedTarget: EventTarget | null;
screenX: number;
screenY: number;
shiftKey: boolean;
}
interface SyntheticKeyboardEvent<T = Element> extends BaseSyntheticEvent<KeyboardEvent, EventTarget & T, EventTarget> {
altKey: boolean;
charCode: number;
ctrlKey: boolean;
key: string;
keyCode: number;
locale: string;
location: number;
metaKey: boolean;
repeat: boolean;
shiftKey: boolean;
which: number;
}
interface SyntheticInputEvent<T = Element> extends BaseSyntheticEvent<InputEvent, EventTarget & T, EventTarget> {
data: string | null;
dataTransfer: DataTransfer | null;
inputType: string;
isComposing: boolean;
}
// ... 更多事件类型2. 事件注册
2.1 事件插件系统
typescript
// 事件插件接口
interface EventPlugin {
eventTypes: Record<string, EventConfig>;
extractEvents(
topLevelType: TopLevelType,
targetInst: Fiber | null,
nativeEvent: Event,
nativeEventTarget: EventTarget | null
): SyntheticEvent | null;
}
// 简单事件插件
const SimpleEventPlugin: EventPlugin = {
eventTypes: {
click: {
phasedRegistrationNames: {
bubbled: 'onClick',
captured: 'onClickCapture'
},
dependencies: ['click']
},
mouseenter: {
registrationName: 'onMouseEnter',
dependencies: ['mouseout', 'mouseover']
},
change: {
phasedRegistrationNames: {
bubbled: 'onChange',
captured: 'onChangeCapture'
},
dependencies: [
'blur',
'change',
'click',
'focus',
'input',
'keydown',
'keyup',
'selectionchange'
]
}
},
extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget) {
const dispatchConfig = this.eventTypes[topLevelType];
if (!dispatchConfig) {
return null;
}
// 创建合成事件
const EventConstructor = getEventConstructor(topLevelType);
const event = EventConstructor.getPooled(
dispatchConfig,
targetInst,
nativeEvent,
nativeEventTarget
);
return event;
}
};
// 变化事件插件
const ChangeEventPlugin: EventPlugin = {
eventTypes: {
change: {
phasedRegistrationNames: {
bubbled: 'onChange',
captured: 'onChangeCapture'
},
dependencies: [
'blur', 'change', 'click', 'focus',
'input', 'keydown', 'keyup', 'selectionchange'
]
}
},
extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget) {
// 特殊处理change事件
const event = createChangeEvent(/* ... */);
return event;
}
};
// 选择事件插件
const SelectEventPlugin: EventPlugin = {
eventTypes: {
select: {
phasedRegistrationNames: {
bubbled: 'onSelect',
captured: 'onSelectCapture'
},
dependencies: [
'focusout',
'contextmenu',
'dragend',
'focusin',
'keydown',
'keyup',
'mousedown',
'mouseup',
'selectionchange'
]
}
},
extractEvents(/* ... */) {
// 处理文本选择
return createSelectEvent(/* ... */);
}
};2.2 注册事件监听
typescript
// React 17+事件注册到root
function listenToAllSupportedEvents(rootContainerElement: EventTarget) {
// 只注册一次
if ((rootContainerElement as any)[rootListenersMarkerKey]) {
return;
}
(rootContainerElement as any)[rootListenersMarkerKey] = true;
// 遍历所有支持的事件
allNativeEvents.forEach(domEventName => {
if (!nonDelegatedEvents.has(domEventName)) {
// 委托事件
listenToNativeEvent(
domEventName,
false, // 冒泡
rootContainerElement
);
listenToNativeEvent(
domEventName,
true, // 捕获
rootContainerElement
);
}
});
// 非委托事件(如scroll, focus)
const ownerDocument = rootContainerElement.ownerDocument || rootContainerElement;
nonDelegatedEvents.forEach(domEventName => {
listenToNativeEvent(
domEventName,
false,
ownerDocument
);
});
}
function listenToNativeEvent(
domEventName: string,
isCapturePhaseListener: boolean,
target: EventTarget
) {
let eventSystemFlags = 0;
if (isCapturePhaseListener) {
eventSystemFlags |= IS_CAPTURE_PHASE;
}
addTrappedEventListener(
target,
domEventName,
eventSystemFlags,
isCapturePhaseListener
);
}
function addTrappedEventListener(
targetContainer: EventTarget,
domEventName: string,
eventSystemFlags: EventSystemFlags,
isCapturePhaseListener: boolean
) {
// 创建监听器
const listener = createEventListenerWrapperWithPriority(
targetContainer,
domEventName,
eventSystemFlags
);
// 添加监听
if (isCapturePhaseListener) {
targetContainer.addEventListener(domEventName, listener, true);
} else {
targetContainer.addEventListener(domEventName, listener, false);
}
}2.3 优先级包装
typescript
function createEventListenerWrapperWithPriority(
targetContainer: EventTarget,
domEventName: string,
eventSystemFlags: EventSystemFlags
): (event: Event) => void {
const eventPriority = getEventPriorityForPluginSystem(domEventName);
let listenerWrapper;
switch (eventPriority) {
case DiscreteEvent:
listenerWrapper = dispatchDiscreteEvent;
break;
case UserBlockingEvent:
listenerWrapper = dispatchUserBlockingUpdate;
break;
case ContinuousEvent:
default:
listenerWrapper = dispatchEvent;
break;
}
return listenerWrapper.bind(
null,
domEventName,
eventSystemFlags,
targetContainer
);
}
function getEventPriorityForPluginSystem(domEventName: string): EventPriority {
switch (domEventName) {
// 离散事件
case 'click':
case 'keydown':
case 'keyup':
case 'input':
case 'change':
case 'submit':
return DiscreteEvent;
// 用户阻塞事件
case 'drag':
case 'dragenter':
case 'dragleave':
case 'dragover':
case 'mouseenter':
case 'mouseleave':
case 'mousemove':
case 'mouseout':
case 'mouseover':
case 'pointerenter':
case 'pointerleave':
case 'pointermove':
case 'pointerout':
case 'pointerover':
case 'scroll':
case 'touchmove':
case 'wheel':
return ContinuousEvent;
// 默认优先级
default:
return ContinuousEvent;
}
}3. 事件触发
3.1 事件分发
typescript
function dispatchEvent(
domEventName: string,
eventSystemFlags: EventSystemFlags,
targetContainer: EventTarget,
nativeEvent: Event
) {
// 获取目标Fiber
const nativeEventTarget = getEventTarget(nativeEvent);
const targetInst = getClosestInstanceFromNode(nativeEventTarget);
// 批量更新上下文
batchedEventUpdates(() => {
dispatchEventsForPlugins(
domEventName,
eventSystemFlags,
nativeEvent,
targetInst,
targetContainer
);
});
}
function dispatchEventsForPlugins(
domEventName: string,
eventSystemFlags: EventSystemFlags,
nativeEvent: Event,
targetInst: null | Fiber,
targetContainer: EventTarget
) {
const nativeEventTarget = getEventTarget(nativeEvent);
// 收集事件路径
const dispatchQueue: DispatchQueue = [];
// 提取事件
extractEvents(
dispatchQueue,
domEventName,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
targetContainer
);
// 处理事件队列
processDispatchQueue(dispatchQueue, eventSystemFlags);
}3.2 提取事件
typescript
function extractEvents(
dispatchQueue: DispatchQueue,
domEventName: string,
targetInst: null | Fiber,
nativeEvent: Event,
nativeEventTarget: null | EventTarget,
eventSystemFlags: EventSystemFlags,
targetContainer: EventTarget
) {
// SimpleEventPlugin提取
const simpleEvent = SimpleEventPlugin.extractEvents(
domEventName,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
targetContainer
);
if (simpleEvent) {
dispatchQueue.push({
event: simpleEvent,
listeners: accumulateSinglePhaseListeners(
targetInst,
simpleEvent.type,
nativeEvent.type
)
});
}
// 其他插件...
}
function accumulateSinglePhaseListeners(
targetFiber: Fiber | null,
reactName: string,
nativeEventType: string
): Array<DispatchListener> {
const captureName = reactName + 'Capture';
const listeners: Array<DispatchListener> = [];
let instance = targetFiber;
while (instance !== null) {
const { stateNode, tag } = instance;
// HostComponent(原生DOM)
if (tag === HostComponent && stateNode !== null) {
const currentTarget = stateNode;
// 捕获阶段监听器
const captureListener = getListener(instance, captureName);
if (captureListener != null) {
listeners.unshift({
instance,
listener: captureListener,
currentTarget
});
}
// 冒泡阶段监听器
const bubbleListener = getListener(instance, reactName);
if (bubbleListener != null) {
listeners.push({
instance,
listener: bubbleListener,
currentTarget
});
}
}
instance = instance.return;
}
return listeners;
}
function getListener(inst: Fiber, registrationName: string): Function | null {
const stateNode = inst.stateNode;
if (stateNode === null) {
return null;
}
const props = getFiberCurrentPropsFromNode(stateNode);
if (props === null) {
return null;
}
const listener = props[registrationName];
if (listener && typeof listener !== 'function') {
throw new Error(
`Expected \`${registrationName}\` listener to be a function, ` +
`instead got a value of \`${typeof listener}\` type.`
);
}
return listener;
}3.3 处理事件队列
typescript
function processDispatchQueue(
dispatchQueue: DispatchQueue,
eventSystemFlags: EventSystemFlags
) {
const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
for (let i = 0; i < dispatchQueue.length; i++) {
const { event, listeners } = dispatchQueue[i];
processDispatchQueueItemsInOrder(
event,
listeners,
inCapturePhase
);
}
}
function processDispatchQueueItemsInOrder(
event: SyntheticEvent,
dispatchListeners: Array<DispatchListener>,
inCapturePhase: boolean
) {
if (inCapturePhase) {
// 捕获阶段: 从根到目标(正序)
for (let i = dispatchListeners.length - 1; i >= 0; i--) {
const { instance, currentTarget, listener } = dispatchListeners[i];
if (event.isPropagationStopped()) {
return;
}
executeDispatch(event, listener, currentTarget);
}
} else {
// 冒泡阶段: 从目标到根(逆序)
for (let i = 0; i < dispatchListeners.length; i++) {
const { instance, currentTarget, listener } = dispatchListeners[i];
if (event.isPropagationStopped()) {
return;
}
executeDispatch(event, listener, currentTarget);
}
}
}
function executeDispatch(
event: SyntheticEvent,
listener: Function,
currentTarget: EventTarget
) {
const type = event.type || 'unknown-event';
event.currentTarget = currentTarget;
try {
listener(event);
} catch (error) {
// 错误处理
console.error(
`Error in ${type} handler: `,
error
);
}
event.currentTarget = null;
}4. 合成事件对象
4.1 SyntheticEvent构造
typescript
// 合成事件基类
function SyntheticEvent(
dispatchConfig: EventConfig,
targetInst: Fiber,
nativeEvent: Event,
nativeEventTarget: EventTarget
) {
this.dispatchConfig = dispatchConfig;
this._targetInst = targetInst;
this.nativeEvent = nativeEvent;
this._dispatchListeners = null;
this._dispatchInstances = null;
// 复制原生事件属性
const Interface = this.constructor.Interface;
for (const propName in Interface) {
if (!Interface.hasOwnProperty(propName)) {
continue;
}
const normalize = Interface[propName];
if (normalize) {
this[propName] = normalize(nativeEvent);
} else {
if (propName === 'target') {
this.target = nativeEventTarget;
} else {
this[propName] = nativeEvent[propName];
}
}
}
// 阻止默认状态
const defaultPrevented =
nativeEvent.defaultPrevented != null
? nativeEvent.defaultPrevented
: nativeEvent.returnValue === false;
if (defaultPrevented) {
this.isDefaultPrevented = functionThatReturnsTrue;
} else {
this.isDefaultPrevented = functionThatReturnsFalse;
}
this.isPropagationStopped = functionThatReturnsFalse;
}
// 方法
Object.assign(SyntheticEvent.prototype, {
preventDefault() {
this.defaultPrevented = true;
const event = this.nativeEvent;
if (!event) {
return;
}
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
this.isDefaultPrevented = functionThatReturnsTrue;
},
stopPropagation() {
const event = this.nativeEvent;
if (!event) {
return;
}
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
this.isPropagationStopped = functionThatReturnsTrue;
},
persist() {
// React 17前用于保持事件对象
this.isPersistent = functionThatReturnsTrue;
},
isPersistent: functionThatReturnsFalse,
destructor() {
const Interface = this.constructor.Interface;
for (const propName in Interface) {
this[propName] = null;
}
this.dispatchConfig = null;
this._targetInst = null;
this.nativeEvent = null;
this.isDefaultPrevented = functionThatReturnsFalse;
this.isPropagationStopped = functionThatReturnsFalse;
this._dispatchListeners = null;
this._dispatchInstances = null;
}
});
// 接口定义
SyntheticEvent.Interface = {
type: null,
target: null,
currentTarget: null,
eventPhase: null,
bubbles: null,
cancelable: null,
timeStamp: (event: Event) => event.timeStamp || Date.now(),
defaultPrevented: null,
isTrusted: null
};4.2 具体事件类型
typescript
// 鼠标事件
function SyntheticMouseEvent(
dispatchConfig,
targetInst,
nativeEvent,
nativeEventTarget
) {
return SyntheticEvent.call(
this,
dispatchConfig,
targetInst,
nativeEvent,
nativeEventTarget
);
}
SyntheticMouseEvent.Interface = {
...SyntheticEvent.Interface,
screenX: null,
screenY: null,
clientX: null,
clientY: null,
pageX: null,
pageY: null,
ctrlKey: null,
shiftKey: null,
altKey: null,
metaKey: null,
getModifierState: getEventModifierState,
button: null,
buttons: null,
relatedTarget: (event: MouseEvent) => {
return event.relatedTarget ||
(event.fromElement === event.srcElement
? event.toElement
: event.fromElement);
},
movementX: (event: MouseEvent) => {
if ('movementX' in event) {
return event.movementX;
}
const screenX = previousScreenX;
previousScreenX = event.screenX;
return isFirst
? 0
: event.screenX - screenX;
},
movementY: (event: MouseEvent) => {
if ('movementY' in event) {
return event.movementY;
}
const screenY = previousScreenY;
previousScreenY = event.screenY;
return isFirst
? 0
: event.screenY - screenY;
}
};
// 键盘事件
function SyntheticKeyboardEvent(/* ... */) {
return SyntheticEvent.call(this, /* ... */);
}
SyntheticKeyboardEvent.Interface = {
...SyntheticEvent.Interface,
key: getEventKey,
code: null,
location: null,
ctrlKey: null,
shiftKey: null,
altKey: null,
metaKey: null,
repeat: null,
locale: null,
getModifierState: getEventModifierState,
charCode: (event: KeyboardEvent) => {
if (event.type === 'keypress') {
return getEventCharCode(event);
}
return 0;
},
keyCode: (event: KeyboardEvent) => {
if (event.type === 'keydown' || event.type === 'keyup') {
return event.keyCode;
}
return 0;
},
which: (event: KeyboardEvent) => {
if (event.type === 'keypress') {
return getEventCharCode(event);
}
if (event.type === 'keydown' || event.type === 'keyup') {
return event.keyCode;
}
return 0;
}
};5. 事件池(React 17前)
5.1 对象池实现
typescript
// 事件对象池
const EVENT_POOL_SIZE = 10;
function addEventPoolingTo(EventConstructor: Function) {
EventConstructor.eventPool = [];
EventConstructor.getPooled = getPooledEvent;
EventConstructor.release = releasePooledEvent;
}
function getPooledEvent(
dispatchConfig: EventConfig,
targetInst: Fiber,
nativeEvent: Event,
nativeEventTarget: EventTarget
) {
const EventConstructor = this;
if (EventConstructor.eventPool.length) {
// 从池中获取
const instance = EventConstructor.eventPool.pop();
EventConstructor.call(
instance,
dispatchConfig,
targetInst,
nativeEvent,
nativeEventTarget
);
return instance;
}
// 创建新实例
return new EventConstructor(
dispatchConfig,
targetInst,
nativeEvent,
nativeEventTarget
);
}
function releasePooledEvent(event: SyntheticEvent) {
const EventConstructor = this;
// 清理事件对象
event.destructor();
// 放回池中
if (EventConstructor.eventPool.length < EVENT_POOL_SIZE) {
EventConstructor.eventPool.push(event);
}
}
// 应用到所有事件类型
addEventPoolingTo(SyntheticEvent);
addEventPoolingTo(SyntheticMouseEvent);
addEventPoolingTo(SyntheticKeyboardEvent);
// ... 其他事件类型5.2 persist方法
typescript
// 使用persist保持事件对象
function handleClick(e: SyntheticMouseEvent) {
// React 17前: 异步访问事件会报错
setTimeout(() => {
console.log(e.type); // Warning: 事件已被回收
}, 100);
// 使用persist保持
e.persist();
setTimeout(() => {
console.log(e.type); // 正常工作
}, 100);
}
// React 17+: 不再需要persist
function handleClick17(e: SyntheticMouseEvent) {
setTimeout(() => {
console.log(e.type); // 正常工作
}, 100);
}6. 特殊事件处理
6.1 Change事件
typescript
// Change事件特殊处理
function extractChangeEvent(
dispatchQueue: DispatchQueue,
domEventName: string,
targetInst: Fiber | null,
nativeEvent: Event,
nativeEventTarget: EventTarget | null
) {
// 不同元素的change行为不同
const targetNode = targetInst ? targetInst.stateNode : null;
if (!targetNode) {
return;
}
const nodeName = targetNode.nodeName && targetNode.nodeName.toLowerCase();
let handler;
switch (nodeName) {
case 'input':
if (targetNode.type === 'checkbox' || targetNode.type === 'radio') {
handler = handleInputChange;
} else {
handler = handleInputValueChange;
}
break;
case 'textarea':
handler = handleTextareaChange;
break;
case 'select':
handler = handleSelectChange;
break;
default:
return;
}
const event = handler(
domEventName,
targetInst,
nativeEvent,
nativeEventTarget
);
if (event) {
dispatchQueue.push({
event,
listeners: accumulateSinglePhaseListeners(
targetInst,
'onChange',
nativeEvent.type
)
});
}
}
// input change处理
function handleInputValueChange(
domEventName: string,
targetInst: Fiber,
nativeEvent: Event,
nativeEventTarget: EventTarget
) {
// 跟踪值变化
const node = nativeEventTarget as HTMLInputElement;
if (
domEventName === 'input' ||
domEventName === 'change'
) {
return createChangeEvent(
domEventName,
targetInst,
nativeEvent,
nativeEventTarget
);
}
return null;
}6.2 Focus和Blur事件
typescript
// Focus/Blur事件处理
function extractFocusEvent(
dispatchQueue: DispatchQueue,
domEventName: string,
targetInst: Fiber | null,
nativeEvent: Event,
nativeEventTarget: EventTarget | null
) {
// React使用focusin/focusout替代focus/blur
// 因为focus/blur不冒泡
if (
domEventName === 'focusin' ||
domEventName === 'focusout'
) {
const event = createFocusEvent(
domEventName === 'focusin' ? 'focus' : 'blur',
targetInst,
nativeEvent,
nativeEventTarget
);
if (event) {
const reactName = domEventName === 'focusin' ? 'onFocus' : 'onBlur';
dispatchQueue.push({
event,
listeners: accumulateSinglePhaseListeners(
targetInst,
reactName,
domEventName
)
});
}
}
}6.3 鼠标移入移出
typescript
// MouseEnter/MouseLeave
function extractMouseEnterLeaveEvent(
dispatchQueue: DispatchQueue,
domEventName: string,
targetInst: Fiber | null,
nativeEvent: MouseEvent,
nativeEventTarget: EventTarget | null
) {
// mouseenter/mouseleave不冒泡
// React通过mouseover/mouseout模拟
const isOverEvent = domEventName === 'mouseover';
const isOutEvent = domEventName === 'mouseout';
if (!isOverEvent && !isOutEvent) {
return;
}
const from = isOutEvent ? nativeEvent.relatedTarget : nativeEventTarget;
const to = isOverEvent ? nativeEvent.relatedTarget : nativeEventTarget;
// 检查是否真的进入/离开
if (from === to) {
return;
}
let leave, enter, leaveEventType, enterEventType;
if (isOverEvent) {
leave = 'onMouseLeave';
enter = 'onMouseEnter';
leaveEventType = 'mouseleave';
enterEventType = 'mouseenter';
} else {
leave = 'onPointerLeave';
enter = 'onPointerEnter';
leaveEventType = 'pointerleave';
enterEventType = 'pointerenter';
}
const fromNode = from == null ? window : from;
const toNode = to == null ? window : to;
const fromInst = getClosestInstanceFromNode(fromNode);
const toInst = getClosestInstanceFromNode(toNode);
// 创建leave事件
const leaveEvent = SyntheticMouseEvent.getPooled(
leaveEventType,
fromInst,
nativeEvent,
nativeEventTarget
);
leaveEvent.type = leaveEventType;
leaveEvent.target = fromNode;
leaveEvent.relatedTarget = toNode;
// 创建enter事件
const enterEvent = SyntheticMouseEvent.getPooled(
enterEventType,
toInst,
nativeEvent,
nativeEventTarget
);
enterEvent.type = enterEventType;
enterEvent.target = toNode;
enterEvent.relatedTarget = fromNode;
dispatchQueue.push(
{ event: leaveEvent, listeners: /* ... */ },
{ event: enterEvent, listeners: /* ... */ }
);
}7. 性能优化
7.1 批量更新
typescript
// 事件触发的批量更新
function batchedEventUpdates<A, R>(fn: (a: A) => R, a: A): R {
const prevExecutionContext = executionContext;
executionContext |= EventContext;
try {
return fn(a);
} finally {
executionContext = prevExecutionContext;
if (executionContext === NoContext) {
// 刷新同步队列
flushSyncCallbackQueue();
}
}
}
// 示例
function handleMultipleUpdates() {
// 这些更新会被批量处理
setCount(c => c + 1);
setName('John');
setAge(25);
// 只触发一次重新渲染
}7.2 事件优先级
typescript
// 不同事件不同优先级
function runWithPriority<T>(priority: EventPriority, fn: () => T): T {
const previousPriority = currentUpdatePriority;
currentUpdatePriority = priority;
try {
return fn();
} finally {
currentUpdatePriority = previousPriority;
}
}
// 离散事件: 同步优先级
function dispatchDiscreteEvent(
domEventName: string,
eventSystemFlags: EventSystemFlags,
container: EventTarget,
nativeEvent: Event
) {
const previousPriority = getCurrentUpdatePriority();
try {
setCurrentUpdatePriority(DiscreteEventPriority);
dispatchEvent(domEventName, eventSystemFlags, container, nativeEvent);
} finally {
setCurrentUpdatePriority(previousPriority);
}
}8. 面试高频问题
typescript
const syntheticEventInterviewQA = {
Q1: {
question: 'React合成事件和原生事件的区别?',
answer: [
'1. 命名: 驼峰 vs 小写',
'2. 处理函数: 函数引用 vs 字符串',
'3. 阻止默认: preventDefault() vs return false',
'4. 事件委托: 委托到根 vs 绑定到元素',
'5. 跨浏览器: React统一处理兼容性',
'6. 对象池: 17前复用对象'
]
},
Q2: {
question: 'React事件委托原理?',
answer: `
React 17+:
1. 所有事件注册到root容器
2. 原生事件冒泡到root
3. React根据target找到对应Fiber
4. 从target向上收集所有监听器
5. 按顺序执行监听器
好处:
- 减少内存占用
- 动态绑定/解绑
- 统一管理
`
},
Q3: {
question: 'React 17事件系统的变化?',
answer: [
'1. 事件委托从document改为root容器',
'2. 废弃事件池,不再需要persist',
'3. 捕获阶段优先级更高',
'4. onScroll不再冒泡',
'5. onFocus/onBlur使用原生事件',
'6. 更好的与第三方库集成'
]
},
Q4: {
question: '如何阻止事件冒泡?',
answer: `
e.stopPropagation() - 阻止React合成事件冒泡
e.nativeEvent.stopImmediatePropagation() - 阻止原生事件
注意:
- stopPropagation只阻止React事件冒泡
- 不影响原生事件监听器
`
},
Q5: {
question: '为什么合成事件不能异步访问(17前)?',
answer: `
事件池机制:
1. 事件对象复用节省内存
2. 事件处理完后清空属性
3. 异步访问时已被回收
解决:
- 使用e.persist()保持
- 或提前保存需要的值
React 17+已废弃事件池
`
},
Q6: {
question: 'onChange事件如何工作?',
answer: [
'1. 监听多个原生事件(input, change, blur等)',
'2. 根据元素类型选择处理逻辑',
'3. input/textarea: 监听input事件',
'4. checkbox/radio: 监听change事件',
'5. select: 监听change事件',
'6. 统一触发onChange'
]
}
};9. 最佳实践
typescript
const eventBestPractices = {
事件绑定: {
推荐: `
// 类组件: 构造器绑定或箭头函数
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
// ...
}
}
// 或使用类字段
handleClick = () => {
// ...
}
// 函数组件: useCallback
const handleClick = useCallback(() => {
// ...
}, []);
`,
避免: `
// ❌ 每次渲染创建新函数
<button onClick={() => handleClick()} />
// ❌ bind每次创建新函数
<button onClick={this.handleClick.bind(this)} />
`
},
事件传参: {
方法1: '箭头函数',
code1: `<button onClick={() => handleClick(id)} />`,
方法2: 'bind',
code2: `<button onClick={handleClick.bind(null, id)} />`,
方法3: 'data属性',
code3: `
<button data-id={id} onClick={handleClick} />
function handleClick(e) {
const id = e.currentTarget.dataset.id;
}
`
},
性能优化: [
'使用事件委托',
'避免内联函数',
'使用useCallback/React.memo',
'合理使用preventDefault',
'大列表使用虚拟滚动'
]
};10. 总结
React合成事件系统的核心要点:
- 跨浏览器: 统一的事件API
- 事件委托: 委托到root容器
- 事件插件: 可扩展的插件系统
- 优先级: 不同事件不同优先级
- 批量更新: 自动批处理
- 特殊处理: onChange、Focus等特殊事件
- React 17变化: 废弃事件池,改进委托
- 性能优化: 事件复用,减少监听器
理解合成事件是掌握React事件处理的基础。