React 事件系统原理
更新: 10/16/2025 字数: 0 字 时长: 0 分钟
概述
React 实现了一套自己的事件系统,称为合成事件(SyntheticEvent)。它不是简单地将事件处理函数绑定到 DOM 元素上,而是通过事件委托在根节点上统一处理所有事件。
为什么需要合成事件系统
浏览器兼容性
javascript
// 原生事件的浏览器差异
if (e.stopPropagation) {
e.stopPropagation(); // 标准浏览器
} else {
e.cancelBubble = true; // IE
}
// React 合成事件统一接口
e.stopPropagation(); // 所有浏览器都一样性能优化
jsx
// 原生方式:每个元素都绑定事件
<ul>
<li onClick={handler1}>Item 1</li>
<li onClick={handler2}>Item 2</li>
<li onClick={handler3}>Item 3</li>
{/* 100 个 li = 100 个事件监听器 */}
</ul>
// React 方式:只在根节点绑定一次
// 所有点击事件都通过事件委托处理跨平台支持
React 的事件系统是抽象层,可以适配不同平台:
- React DOM:浏览器事件
- React Native:原生移动事件
- React ART:Canvas 事件
合成事件的架构
事件委托
React 17 之前,所有事件委托到 document:
javascript
// React 16 及之前
document.addEventListener('click', dispatchEvent);
document.addEventListener('change', dispatchEvent);
// ...React 17 之后,事件委托到 React 根节点:
javascript
// React 17+
const rootNode = document.getElementById('root');
rootNode.addEventListener('click', dispatchEvent);
rootNode.addEventListener('change', dispatchEvent);为什么改变委托位置?
jsx
// React 17+ 支持多个 React 应用共存
<div id="app1"></div>
<div id="app2"></div>
ReactDOM.render(<App1 />, document.getElementById('app1'));
ReactDOM.render(<App2 />, document.getElementById('app2'));
// 每个应用的事件互不干扰事件注册流程
1. 事件插件系统
React 使用插件系统来处理不同类型的事件:
javascript
// 事件插件
const SimpleEventPlugin = {
// 事件名称映射
eventTypes: {
click: {
phasedRegistrationNames: {
bubbled: 'onClick',
captured: 'onClickCapture',
},
dependencies: ['click'],
},
// ...其他事件
},
// 提取事件
extractEvents(
topLevelType,
targetInst,
nativeEvent,
nativeEventTarget
) {
const dispatchConfig = eventTypes[topLevelType];
if (!dispatchConfig) return null;
// 创建合成事件
const event = SyntheticEvent.getPooled(
dispatchConfig,
targetInst,
nativeEvent,
nativeEventTarget
);
return event;
},
};2. 注册事件监听
javascript
// 简化的事件注册流程
function listenToAllSupportedEvents(rootContainerElement) {
// 注册所有支持的事件
allNativeEvents.forEach(domEventName => {
// 捕获阶段
registerTwoPhaseEvent(
domEventName + 'Capture',
[domEventName],
true // useCapture
);
// 冒泡阶段
registerTwoPhaseEvent(
domEventName,
[domEventName],
false
);
});
}
function registerTwoPhaseEvent(
registrationName,
dependencies,
useCapture
) {
dependencies.forEach(dependency => {
addTrappedEventListener(
rootContainerElement,
dependency,
useCapture
);
});
}
function addTrappedEventListener(
targetContainer,
domEventName,
isCapturePhaseListener
) {
const listener = createEventListenerWrapperWithPriority(
targetContainer,
domEventName,
eventSystemFlags
);
targetContainer.addEventListener(
domEventName,
listener,
isCapturePhaseListener
);
}合成事件对象
SyntheticEvent 的结构
javascript
class SyntheticEvent {
constructor(
dispatchConfig,
targetInst,
nativeEvent,
nativeEventTarget
) {
this.dispatchConfig = dispatchConfig;
this._targetInst = targetInst;
this.nativeEvent = nativeEvent;
this.target = nativeEventTarget;
this.currentTarget = null;
// 从原生事件复制属性
const Interface = this.constructor.Interface;
for (const propName in Interface) {
const normalize = Interface[propName];
if (normalize) {
this[propName] = normalize(nativeEvent);
} else {
this[propName] = nativeEvent[propName];
}
}
// 默认未阻止
this.isDefaultPrevented = () => false;
this.isPropagationStopped = () => false;
}
preventDefault() {
this.isDefaultPrevented = () => true;
const event = this.nativeEvent;
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
}
stopPropagation() {
this.isPropagationStopped = () => true;
const event = this.nativeEvent;
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
}
}
// 不同类型事件的接口
SyntheticEvent.Interface = {
type: null,
target: null,
currentTarget: null,
eventPhase: null,
bubbles: null,
cancelable: null,
timeStamp: (event) => event.timeStamp || Date.now(),
// ...
};
// 鼠标事件
class SyntheticMouseEvent extends SyntheticEvent {}
SyntheticMouseEvent.Interface = {
...SyntheticEvent.Interface,
screenX: null,
screenY: null,
clientX: null,
clientY: null,
pageX: null,
pageY: null,
// ...
};
// 键盘事件
class SyntheticKeyboardEvent extends SyntheticEvent {}
SyntheticKeyboardEvent.Interface = {
...SyntheticEvent.Interface,
key: null,
code: null,
location: null,
ctrlKey: null,
shiftKey: null,
altKey: null,
metaKey: null,
// ...
};事件池(React 16 及之前)
javascript
// React 16 的事件池机制(已在 React 17 移除)
const EVENT_POOL_SIZE = 10;
class SyntheticEvent {
static getPooled(/* ... */) {
const EventConstructor = this;
// 从池中获取
if (EventConstructor.eventPool.length) {
const instance = EventConstructor.eventPool.pop();
EventConstructor.call(instance, /* ... */);
return instance;
}
// 池中没有,创建新的
return new EventConstructor(/* ... */);
}
destructor() {
// 清空所有属性
const EventConstructor = this.constructor;
for (const propName in EventConstructor.Interface) {
this[propName] = null;
}
// 放回池中
if (EventConstructor.eventPool.length < EVENT_POOL_SIZE) {
EventConstructor.eventPool.push(this);
}
}
}
// 使用示例(React 16)
function handleClick(e) {
console.log(e.type); // 'click'
setTimeout(() => {
console.log(e.type); // null(事件已被回收)
}, 0);
// 如果需要异步访问,必须调用 persist
e.persist();
setTimeout(() => {
console.log(e.type); // 'click'(事件未被回收)
}, 0);
}React 17 移除了事件池,因为:
- 现代浏览器性能足够好
- 事件池带来的性能提升不明显
- 容易造成 bug(忘记 persist)
事件分发流程
1. 原生事件触发
javascript
// 用户点击 button
<div id="root">
<div id="parent">
<button id="child">Click me</button>
</div>
</div>
// 原生事件流:
// 1. 捕获:document → root → parent → button
// 2. 冒泡:button → parent → root → document2. 收集事件处理函数
javascript
function dispatchEventsForPlugins(
domEventName,
eventSystemFlags,
nativeEvent,
targetInst,
targetContainer
) {
// 1. 获取事件目标对应的 Fiber 节点
const nativeEventTarget = getEventTarget(nativeEvent);
const targetFiber = getClosestInstanceFromNode(nativeEventTarget);
// 2. 收集从目标到根的所有事件处理函数
const dispatchQueue = [];
// 提取事件(创建合成事件)
extractEvents(
dispatchQueue,
domEventName,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
targetContainer
);
// 3. 执行事件处理函数
processDispatchQueue(dispatchQueue, eventSystemFlags);
}
function extractEvents(
dispatchQueue,
domEventName,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
targetContainer
) {
// 创建合成事件
const syntheticEvent = createSyntheticEvent(
domEventName,
nativeEvent,
nativeEventTarget
);
// 收集事件处理函数
const listeners = accumulateSinglePhaseListeners(
targetInst,
domEventName,
nativeEvent.type,
inCapturePhase
);
if (listeners.length > 0) {
dispatchQueue.push({
event: syntheticEvent,
listeners: listeners,
});
}
}3. 收集两个阶段的监听器
javascript
function accumulateSinglePhaseListeners(
targetFiber,
reactName,
nativeEventType,
inCapturePhase
) {
const captureName = reactName + 'Capture';
const reactEventName = inCapturePhase ? captureName : reactName;
const listeners = [];
let instance = targetFiber;
// 从目标节点向上遍历到根节点
while (instance !== null) {
const { stateNode, tag } = instance;
// 只处理 DOM 节点
if (tag === HostComponent && stateNode !== null) {
const listener = getListener(instance, reactEventName);
if (listener != null) {
listeners.push({
instance,
listener,
currentTarget: stateNode,
});
}
}
instance = instance.return;
}
// 如果是捕获阶段,需要反转数组(从根到目标)
if (inCapturePhase) {
return listeners.reverse();
}
return listeners;
}
// 示例:
// <div onClick={divClick} onClickCapture={divClickCapture}>
// <button onClick={btnClick} onClickCapture={btnClickCapture}>
// Click
// </button>
// </div>
// 点击 button 时收集到的监听器:
// 捕获阶段:[divClickCapture, btnClickCapture]
// 冒泡阶段:[btnClick, divClick]4. 执行事件处理函数
javascript
function processDispatchQueue(
dispatchQueue,
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,
dispatchListeners,
inCapturePhase
) {
if (inCapturePhase) {
// 捕获阶段:从根到目标
for (let i = dispatchListeners.length - 1; i >= 0; i--) {
const { listener, currentTarget } = dispatchListeners[i];
if (event.isPropagationStopped()) {
return;
}
executeDispatch(event, listener, currentTarget);
}
} else {
// 冒泡阶段:从目标到根
for (let i = 0; i < dispatchListeners.length; i++) {
const { listener, currentTarget } = dispatchListeners[i];
if (event.isPropagationStopped()) {
return;
}
executeDispatch(event, listener, currentTarget);
}
}
}
function executeDispatch(event, listener, currentTarget) {
event.currentTarget = currentTarget;
try {
listener(event);
} catch (error) {
// 错误处理
reportGlobalError(error);
}
event.currentTarget = null;
}事件优先级
离散事件 vs 连续事件
React 18 引入了事件优先级的概念:
javascript
// 离散事件(Discrete Events)- 高优先级
// 用户交互,需要立即响应
const discreteEvents = [
'click',
'keydown',
'keyup',
'input',
'change',
'submit',
// ...
];
// 连续事件(Continuous Events)- 普通优先级
// 持续触发的事件
const continuousEvents = [
'scroll',
'mousemove',
'touchmove',
'wheel',
// ...
];
function getEventPriority(domEventName) {
switch (domEventName) {
case 'click':
case 'keydown':
case 'keyup':
return DiscreteEventPriority; // 高优先级
case 'scroll':
case 'mousemove':
return ContinuousEventPriority; // 普通优先级
default:
return DefaultEventPriority;
}
}优先级的应用
javascript
function dispatchDiscreteEvent(
domEventName,
eventSystemFlags,
container,
nativeEvent
) {
// 获取之前的优先级
const previousPriority = getCurrentUpdatePriority();
try {
// 设置为离散事件优先级(高优先级)
setCurrentUpdatePriority(DiscreteEventPriority);
// 分发事件
dispatchEvent(
domEventName,
eventSystemFlags,
container,
nativeEvent
);
} finally {
// 恢复之前的优先级
setCurrentUpdatePriority(previousPriority);
}
}
// 使用示例
function App() {
const [text, setText] = useState('');
// 离散事件(input),高优先级,立即更新
const handleChange = (e) => {
setText(e.target.value);
};
return <input value={text} onChange={handleChange} />;
}特殊事件处理
onChange 事件
React 的 onChange 不同于原生的 onchange:
javascript
// 原生 onchange:失焦时触发
<input onchange="handleChange()" />
// React onChange:每次输入都触发
<input onChange={handleChange} />
// React 内部实现
function extractEvents(/* ... */) {
if (domEventName === 'input' || domEventName === 'change') {
// input 和 change 都映射到 onChange
return createChangeEvent(/* ... */);
}
}onScroll 事件
scroll 事件不冒泡,React 需要特殊处理:
javascript
function registerSimpleEvents() {
registerDirectEvent('scroll', 'onScroll');
// scroll 直接在目标元素上监听,不使用委托
}表单事件
jsx
function Form() {
const handleSubmit = (e) => {
// 阻止默认提交行为
e.preventDefault();
// 获取表单数据
const formData = new FormData(e.target);
console.log(Object.fromEntries(formData));
};
return (
<form onSubmit={handleSubmit}>
<input name="username" />
<button type="submit">提交</button>
</form>
);
}React 事件 vs 原生事件
主要区别
jsx
function Component() {
useEffect(() => {
const button = document.getElementById('btn');
// 原生事件:直接绑定
button.addEventListener('click', (e) => {
console.log('原生事件');
});
}, []);
// React 合成事件
const handleClick = (e) => {
console.log('React 事件');
// e 是 SyntheticEvent,不是原生 Event
};
return <button id="btn" onClick={handleClick}>Click</button>;
}
// 执行顺序(React 17+):
// 1. 原生事件(捕获)
// 2. 原生事件(目标)
// 3. 原生事件(冒泡)
// 4. React 事件(捕获)
// 5. React 事件(冒泡)阻止事件传播的影响
jsx
function App() {
useEffect(() => {
document.addEventListener('click', () => {
console.log('document 原生事件');
});
}, []);
const handleDivClick = (e) => {
console.log('div React 事件');
e.stopPropagation(); // 只阻止 React 事件冒泡
};
const handleButtonClick = (e) => {
console.log('button React 事件');
};
return (
<div onClick={handleDivClick}>
<button onClick={handleButtonClick}>Click</button>
</div>
);
}
// 点击 button 输出:
// 1. "button React 事件"
// 2. "document 原生事件"(原生事件不受影响)
// 3. "div React 事件" 不会输出(被 stopPropagation 阻止)同时使用原生和合成事件
jsx
function Component() {
const buttonRef = useRef();
useEffect(() => {
const button = buttonRef.current;
const nativeHandler = (e) => {
console.log('原生事件');
e.stopImmediatePropagation(); // 阻止同一元素的其他监听器
};
button.addEventListener('click', nativeHandler);
return () => {
button.removeEventListener('click', nativeHandler);
};
}, []);
const handleClick = (e) => {
console.log('React 事件'); // 不会执行
};
return <button ref={buttonRef} onClick={handleClick}>Click</button>;
}最佳实践
1. 不要混用原生事件和 React 事件
jsx
// ❌ 避免
function Bad() {
const divRef = useRef();
useEffect(() => {
divRef.current.addEventListener('click', handler);
}, []);
return <div ref={divRef} onClick={handler}>Click</div>;
}
// ✅ 推荐:统一使用 React 事件
function Good() {
return <div onClick={handler}>Click</div>;
}2. 事件处理函数的性能优化
jsx
// ❌ 避免:每次渲染都创建新函数
function Bad({ id }) {
return (
<button onClick={() => handleClick(id)}>
Click
</button>
);
}
// ✅ 推荐:使用 useCallback
function Good({ id }) {
const handleClick = useCallback(() => {
doSomething(id);
}, [id]);
return <button onClick={handleClick}>Click</button>;
}
// 或者使用 data 属性
function Better({ id }) {
const handleClick = (e) => {
const id = e.currentTarget.dataset.id;
doSomething(id);
};
return <button data-id={id} onClick={handleClick}>Click</button>;
}3. 阻止默认行为
jsx
function Form() {
const handleSubmit = (e) => {
// ✅ React 中必须显式调用
e.preventDefault();
// ❌ React 中 return false 不起作用
// return false;
};
return <form onSubmit={handleSubmit}>{/* ... */}</form>;
}4. 事件委托模式
jsx
// ✅ 利用 React 的事件委托
function List({ items }) {
const handleClick = (e) => {
const id = e.currentTarget.dataset.id;
handleItemClick(id);
};
return (
<ul>
{items.map(item => (
<li key={item.id} data-id={item.id} onClick={handleClick}>
{item.name}
</li>
))}
</ul>
);
}总结
React 事件系统的核心特点:
合成事件
- 跨浏览器兼容
- 统一的事件接口
- 性能优化
事件委托
- React 17 之前委托到 document
- React 17+ 委托到根容器
- 减少内存占用
事件优先级
- 离散事件(高优先级)
- 连续事件(普通优先级)
- 支持并发特性
事件池(已废弃)
- React 16 及之前使用
- React 17+ 已移除
理解 React 事件系统有助于:
- 正确处理事件冒泡和捕获
- 避免原生事件和合成事件混用的问题
- 优化事件处理性能
- 理解 React 的优先级机制