Appearance
第13章 事件系统
本章将深入React的事件系统,理解合成事件的设计理念、事件委托机制、事件注册与触发流程,以及事件优先级如何与调度系统集成。
在前端开发中,事件处理是最常见的交互方式。用户点击按钮、输入文字、滚动页面,这些都会触发相应的事件。React并没有直接使用浏览器原生事件,而是实现了一套自己的事件系统——合成事件(Synthetic Events)。
为什么React要重新实现事件系统?合成事件与原生事件有什么区别?React如何通过事件委托优化性能?事件优先级如何影响更新调度?
本章将逐一解答这些问题,带你深入理解React事件系统的设计与实现。
13.1 React事件系统概述
React事件系统是React与用户交互的桥梁,它在浏览器原生事件之上构建了一层抽象。
13.1.1 为什么需要合成事件
在React出现之前,开发者直接使用浏览器原生事件。但原生事件存在一些问题:
1. 浏览器兼容性问题
不同浏览器的事件实现存在差异:
- IE使用
attachEvent,其他浏览器使用addEventListener - 事件对象的属性名不一致(如
event.targetvsevent.srcElement) - 阻止默认行为的方法不同(
preventDefault()vsreturnValue = false)
2. 性能问题
如果为每个DOM节点都绑定事件监听器,会消耗大量内存:
javascript
// 传统方式:为1000个按钮绑定事件
for (let i = 0; i < 1000; i++) {
document.getElementById(`btn-${i}`)
.addEventListener('click', handleClick);
}
// 需要创建1000个事件监听器3. 事件处理的一致性
React需要在不同平台(Web、Native)上提供一致的事件API。
React的解决方案:合成事件
React实现了一套合成事件系统,解决了上述问题:
javascript
// React合成事件
function MyButton() {
const handleClick = (e) => {
// e是React的合成事件对象(SyntheticEvent)
console.log(e.type); // "click"
console.log(e.target); // 目标DOM节点
console.log(e.nativeEvent); // 原生事件对象
e.preventDefault(); // 统一的API
e.stopPropagation(); // 统一的API
};
return <button onClick={handleClick}>点击我</button>;
}合成事件的优势
- 跨浏览器兼容:统一的事件对象接口
- 性能优化:通过事件委托减少内存占用
- 与React更新机制集成:支持事件优先级
- 事件池优化:复用事件对象(React 17之前)
13.1.2 事件委托机制
React使用事件委托(Event Delegation)来优化性能。
什么是事件委托?
事件委托利用了事件冒泡机制:不在每个子元素上绑定事件,而是在父元素上统一监听。
React的事件委托实现
在React 17之前,所有事件都委托到document上:
javascript
// React 16及之前
document.addEventListener('click', dispatchEvent);
document.addEventListener('change', dispatchEvent);
// ...从React 17开始,事件委托到React根容器上:
javascript
// React 17+
const rootContainer = document.getElementById('root');
rootContainer.addEventListener('click', dispatchEvent);
rootContainer.addEventListener('change', dispatchEvent);
// ...为什么改为委托到根容器?
- 支持多个React应用共存:避免不同React版本冲突
- 更好的事件隔离:不同根容器的事件互不干扰
- 渐进式迁移:方便从旧版本升级
事件委托的工作流程
示例:事件委托的内存优势
javascript
// 传统方式:1000个按钮 = 1000个监听器
function TraditionalList() {
const buttons = [];
for (let i = 0; i < 1000; i++) {
const btn = document.createElement('button');
btn.addEventListener('click', () => {
console.log(`Button ${i} clicked`);
});
buttons.push(btn);
}
return buttons;
}
// React方式:1000个按钮 = 1个监听器(在根容器上)
function ReactList() {
const items = Array.from({ length: 1000 }, (_, i) => i);
return (
<div>
{items.map(i => (
<button key={i} onClick={() => console.log(`Button ${i} clicked`)}>
Button {i}
</button>
))}
</div>
);
}在React中,无论有多少个按钮,根容器上只有一个click事件监听器。
13.1.3 合成事件对象
React的合成事件对象(SyntheticEvent)是对原生事件的封装。
SyntheticEvent的结构
javascript
// 文件:packages/react-dom-bindings/src/events/SyntheticEvent.js
// 行号:50-100(React 19.3.0)
function SyntheticEvent(
dispatchConfig,
targetInst,
nativeEvent,
nativeEventTarget,
) {
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) {
const normalize = Interface[propName];
if (normalize) {
this[propName] = normalize(nativeEvent);
} else {
this[propName] = nativeEvent[propName];
}
}
// 标准化target和currentTarget
this.target = nativeEventTarget;
this.currentTarget = null;
return this;
}合成事件的关键属性
| 属性 | 说明 | 示例 |
|---|---|---|
type | 事件类型 | "click", "change" |
target | 触发事件的DOM节点 | <button> |
currentTarget | 当前处理事件的DOM节点 | <div> |
nativeEvent | 原生事件对象 | MouseEvent |
bubbles | 是否冒泡 | true |
cancelable | 是否可取消 | true |
defaultPrevented | 是否已阻止默认行为 | false |
eventPhase | 事件阶段 | 3(冒泡阶段) |
isTrusted | 是否由用户触发 | true |
timeStamp | 事件时间戳 | 1234567890 |
合成事件的方法
javascript
// 阻止默认行为
event.preventDefault();
// 阻止事件冒泡
event.stopPropagation();
// 立即停止事件传播(不执行后续监听器)
event.stopImmediatePropagation();
// 检查是否已阻止默认行为
if (event.defaultPrevented) {
// ...
}
// 访问原生事件
const nativeEvent = event.nativeEvent;示例:合成事件与原生事件的对比
javascript
function EventComparison() {
const handleClick = (syntheticEvent) => {
console.log('=== 合成事件 ===');
console.log('类型:', syntheticEvent.type);
console.log('目标:', syntheticEvent.target);
console.log('时间戳:', syntheticEvent.timeStamp);
console.log('=== 原生事件 ===');
const nativeEvent = syntheticEvent.nativeEvent;
console.log('类型:', nativeEvent.type);
console.log('目标:', nativeEvent.target);
console.log('时间戳:', nativeEvent.timeStamp);
// 合成事件和原生事件的时间戳可能不同
// 因为合成事件是在原生事件触发后创建的
};
return <button onClick={handleClick}>点击查看事件对象</button>;
}React 17的重要变更:移除事件池
在React 16及之前,合成事件对象会被放入事件池中复用,以提高性能:
javascript
// React 16:事件对象会被复用
function handleClick(e) {
console.log(e.type); // "click"
setTimeout(() => {
console.log(e.type); // null(事件对象已被清空)
}, 100);
}
// 如果需要异步访问,必须调用persist()
function handleClick(e) {
e.persist(); // 从事件池中移除,保留事件对象
setTimeout(() => {
console.log(e.type); // "click"(仍然可用)
}, 100);
}从React 17开始,移除了事件池机制:
javascript
// React 17+:事件对象不会被复用
function handleClick(e) {
console.log(e.type); // "click"
setTimeout(() => {
console.log(e.type); // "click"(仍然可用)
}, 100);
// 不再需要e.persist()
}为什么移除事件池?
- 现代浏览器性能提升:创建对象的成本已经很低
- 避免混淆:事件池机制容易导致bug
- 简化代码:不需要记住何时调用
persist()
13.2 事件注册与触发
React事件系统的核心流程包括事件注册和事件触发两个阶段。
13.2.1 listenToAllSupportedEvents
React在应用启动时,会在根容器上注册所有支持的事件。
事件注册的时机
javascript
// 文件:packages/react-dom-bindings/src/client/ReactDOMRoot.js
// 行号:150-200(React 19.3.0)
export function createRoot(
container: Element | Document | DocumentFragment,
options?: CreateRootOptions,
): RootType {
// 创建FiberRoot
const root = createContainer(
container,
ConcurrentRoot,
null,
isStrictMode,
concurrentUpdatesByDefaultOverride,
identifierPrefix,
onRecoverableError,
transitionCallbacks,
);
// 标记容器
markContainerAsRoot(root.current, container);
// 注册所有支持的事件
const rootContainerElement =
container.nodeType === COMMENT_NODE
? container.parentNode
: container;
listenToAllSupportedEvents(rootContainerElement);
return new ReactDOMRoot(root);
}listenToAllSupportedEvents实现
javascript
// 文件:packages/react-dom-bindings/src/events/DOMPluginEventSystem.js
// 行号:200-250(React 19.3.0)
export function listenToAllSupportedEvents(rootContainerElement: EventTarget) {
// 避免重复注册
if (!(rootContainerElement: any)[listeningMarker]) {
(rootContainerElement: any)[listeningMarker] = true;
// 遍历所有支持的事件类型
allNativeEvents.forEach((domEventName) => {
// 某些事件不需要在根容器上监听
if (domEventName !== 'selectionchange') {
// 注册捕获阶段和冒泡阶段的监听器
if (!nonDelegatedEvents.has(domEventName)) {
listenToNativeEvent(domEventName, false, rootContainerElement);
}
listenToNativeEvent(domEventName, true, rootContainerElement);
}
});
// 特殊处理selectionchange事件
const ownerDocument =
(rootContainerElement: any).nodeType === DOCUMENT_NODE
? rootContainerElement
: (rootContainerElement: any).ownerDocument;
if (ownerDocument !== null) {
if (!(ownerDocument: any)[listeningMarker]) {
(ownerDocument: any)[listeningMarker] = true;
listenToNativeEvent('selectionchange', false, ownerDocument);
}
}
}
}支持的事件类型
React支持的原生事件存储在allNativeEvents集合中:
javascript
// 文件:packages/react-dom-bindings/src/events/DOMPluginEventSystem.js
// 行号:100-150(React 19.3.0)
const allNativeEvents: Set<DOMEventName> = new Set();
// 注册简单事件
registerSimpleEvents();
// 注册其他事件插件
registerEvents();
// allNativeEvents包含:
// - 鼠标事件:click, mousedown, mouseup, mousemove, ...
// - 键盘事件:keydown, keyup, keypress
// - 表单事件:change, input, submit, reset
// - 焦点事件:focus, blur, focusin, focusout
// - 触摸事件:touchstart, touchmove, touchend, touchcancel
// - 滚动事件:scroll
// - 拖拽事件:drag, dragstart, dragend, drop, ...
// - 媒体事件:play, pause, ended, ...
// - 其他事件:load, error, resize, ...listenToNativeEvent实现
javascript
// 文件:packages/react-dom-bindings/src/events/DOMPluginEventSystem.js
// 行号:300-350(React 19.3.0)
export function listenToNativeEvent(
domEventName: DOMEventName,
isCapturePhaseListener: boolean,
target: EventTarget,
): void {
let eventSystemFlags = 0;
if (isCapturePhaseListener) {
eventSystemFlags |= IS_CAPTURE_PHASE;
}
// 添加事件监听器
addTrappedEventListener(
target,
domEventName,
eventSystemFlags,
isCapturePhaseListener,
);
}
function addTrappedEventListener(
targetContainer: EventTarget,
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
isCapturePhaseListener: boolean,
) {
// 创建监听器函数
const listener = createEventListenerWrapperWithPriority(
targetContainer,
domEventName,
eventSystemFlags,
);
// 添加到DOM
if (isCapturePhaseListener) {
targetContainer.addEventListener(domEventName, listener, true);
} else {
targetContainer.addEventListener(domEventName, listener, false);
}
}事件注册流程图
13.2.2 dispatchEvent入口
当用户触发事件时,React的事件监听器会被调用,进入dispatchEvent流程。
createEventListenerWrapperWithPriority
根据事件类型,创建不同优先级的监听器:
javascript
// 文件:packages/react-dom-bindings/src/events/ReactDOMEventListener.js
// 行号:100-150(React 19.3.0)
export function createEventListenerWrapperWithPriority(
targetContainer: EventTarget,
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
): Function {
// 根据事件名称获取优先级
const eventPriority = getEventPriority(domEventName);
let listenerWrapper;
switch (eventPriority) {
case DiscreteEventPriority:
// 离散事件:click, keydown, input等
listenerWrapper = dispatchDiscreteEvent;
break;
case ContinuousEventPriority:
// 连续事件:mousemove, scroll等
listenerWrapper = dispatchContinuousEvent;
break;
case DefaultEventPriority:
default:
// 默认优先级
listenerWrapper = dispatchEvent;
break;
}
return listenerWrapper.bind(
null,
domEventName,
eventSystemFlags,
targetContainer,
);
}dispatchEvent实现
javascript
// 文件:packages/react-dom-bindings/src/events/ReactDOMEventListener.js
// 行号:200-250(React 19.3.0)
function dispatchEvent(
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
targetContainer: EventTarget,
nativeEvent: AnyNativeEvent,
): void {
// 1. 获取事件目标
const nativeEventTarget = getEventTarget(nativeEvent);
// 2. 查找最近的Fiber节点
const targetInst = getClosestInstanceFromNode(nativeEventTarget);
// 3. 分发事件到React事件系统
dispatchEventForPluginEventSystem(
domEventName,
eventSystemFlags,
nativeEvent,
targetInst,
targetContainer,
);
}13.2.3 合成事件构造
React需要从原生事件构造合成事件对象,并收集事件路径上的所有处理函数。
dispatchEventForPluginEventSystem
javascript
// 文件:packages/react-dom-bindings/src/events/DOMPluginEventSystem.js
// 行号:400-500(React 19.3.0)
export function dispatchEventForPluginEventSystem(
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
nativeEvent: AnyNativeEvent,
targetInst: null | Fiber,
targetContainer: EventTarget,
): void {
let ancestorInst = targetInst;
// 批量更新
batchedUpdates(() => {
// 分发事件
dispatchEventsForPlugins(
domEventName,
eventSystemFlags,
nativeEvent,
ancestorInst,
targetContainer,
);
});
}
function dispatchEventsForPlugins(
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
nativeEvent: AnyNativeEvent,
targetInst: null | Fiber,
targetContainer: EventTarget,
): void {
const nativeEventTarget = getEventTarget(nativeEvent);
// 创建事件队列
const dispatchQueue: DispatchQueue = [];
// 提取事件(收集事件处理函数)
extractEvents(
dispatchQueue,
domEventName,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
targetContainer,
);
// 处理事件队列
processDispatchQueue(dispatchQueue, eventSystemFlags);
}extractEvents:收集事件处理函数
javascript
// 文件:packages/react-dom-bindings/src/events/DOMPluginEventSystem.js
// 行号:550-650(React 19.3.0)
function extractEvents(
dispatchQueue: DispatchQueue,
domEventName: DOMEventName,
targetInst: null | Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: null | EventTarget,
eventSystemFlags: EventSystemFlags,
targetContainer: EventTarget,
): void {
// 根据事件类型选择对应的插件
// 例如:SimpleEventPlugin处理click、input等简单事件
// ChangeEventPlugin处理change事件
// SelectEventPlugin处理select事件
// 以SimpleEventPlugin为例
const reactName = topLevelEventsToReactNames.get(domEventName);
if (reactName === undefined) {
return;
}
// 创建合成事件对象
let SyntheticEventCtor = SyntheticEvent;
switch (domEventName) {
case 'click':
case 'mousedown':
case 'mouseup':
SyntheticEventCtor = SyntheticMouseEvent;
break;
case 'keydown':
case 'keyup':
SyntheticEventCtor = SyntheticKeyboardEvent;
break;
case 'focusin':
case 'focusout':
SyntheticEventCtor = SyntheticFocusEvent;
break;
// ... 其他事件类型
}
// 收集事件路径上的监听器
const listeners = accumulateSinglePhaseListeners(
targetInst,
reactName,
nativeEvent.type,
eventSystemFlags & IS_CAPTURE_PHASE,
);
if (listeners.length > 0) {
// 创建合成事件
const event = new SyntheticEventCtor(
reactName,
domEventName,
null,
nativeEvent,
nativeEventTarget,
);
// 添加到分发队列
dispatchQueue.push({
event,
listeners,
});
}
}accumulateSinglePhaseListeners:收集监听器
javascript
// 文件:packages/react-dom-bindings/src/events/DOMPluginEventSystem.js
// 行号:700-800(React 19.3.0)
export function accumulateSinglePhaseListeners(
targetFiber: Fiber | null,
reactName: string | null,
nativeEventType: string,
isCapturePhase: boolean,
): Array<DispatchListener> {
const captureName = reactName !== null ? reactName + 'Capture' : null;
const reactEventName = isCapturePhase ? captureName : reactName;
const listeners: Array<DispatchListener> = [];
let instance = targetFiber;
let lastHostComponent = null;
// 从目标Fiber向上遍历到根节点
while (instance !== null) {
const {stateNode, tag} = instance;
// 只处理HostComponent(DOM节点)
if (tag === HostComponent && stateNode !== null) {
lastHostComponent = stateNode;
// 获取该节点上的事件处理函数
if (reactEventName !== null) {
const listener = getListener(instance, reactEventName);
if (listener != null) {
listeners.push(
createDispatchListener(instance, listener, lastHostComponent),
);
}
}
}
// 向上遍历
instance = instance.return;
}
return listeners;
}processDispatchQueue:执行事件处理函数
javascript
// 文件:packages/react-dom-bindings/src/events/DOMPluginEventSystem.js
// 行号:850-900(React 19.3.0)
export function processDispatchQueue(
dispatchQueue: DispatchQueue,
eventSystemFlags: EventSystemFlags,
): void {
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: ReactSyntheticEvent,
dispatchListeners: Array<DispatchListener>,
inCapturePhase: boolean,
): void {
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: ReactSyntheticEvent,
listener: Function,
currentTarget: EventTarget,
): void {
event.currentTarget = currentTarget;
try {
listener(event);
} catch (error) {
// 错误处理
}
event.currentTarget = null;
}事件触发完整流程图
示例:事件冒泡与捕获
javascript
function EventPropagation() {
const handleParentClick = (e) => {
console.log('父元素 - 冒泡阶段');
};
const handleParentClickCapture = (e) => {
console.log('父元素 - 捕获阶段');
};
const handleChildClick = (e) => {
console.log('子元素 - 冒泡阶段');
// e.stopPropagation(); // 取消注释可阻止冒泡
};
const handleChildClickCapture = (e) => {
console.log('子元素 - 捕获阶段');
};
return (
<div
onClick={handleParentClick}
onClickCapture={handleParentClickCapture}
style={{ padding: '20px', background: 'lightblue' }}
>
父元素
<button
onClick={handleChildClick}
onClickCapture={handleChildClickCapture}
style={{ margin: '10px' }}
>
子元素
</button>
</div>
);
}
// 点击按钮,输出顺序:
// 1. 父元素 - 捕获阶段
// 2. 子元素 - 捕获阶段
// 3. 子元素 - 冒泡阶段
// 4. 父元素 - 冒泡阶段React事件与原生事件的执行顺序
javascript
function EventOrder() {
useEffect(() => {
// 原生事件监听器
const button = document.getElementById('myButton');
button.addEventListener('click', () => {
console.log('原生事件 - 冒泡');
});
document.addEventListener('click', () => {
console.log('document - 原生事件');
});
}, []);
const handleReactClick = () => {
console.log('React合成事件');
};
return (
<button id="myButton" onClick={handleReactClick}>
点击测试
</button>
);
}
// React 17+的执行顺序:
// 1. 原生事件 - 冒泡(在button上)
// 2. React合成事件(在根容器上)
// 3. document - 原生事件(在document上)
// React 16的执行顺序:
// 1. 原生事件 - 冒泡(在button上)
// 2. React合成事件(在document上)
// 3. document - 原生事件(在document上,但React已处理)13.3 事件优先级
React的事件系统与调度系统紧密集成,不同类型的事件具有不同的优先级。
13.3.1 离散事件与连续事件
React将事件分为三类,每类对应不同的优先级。
事件优先级分类
javascript
// 文件:packages/react-dom-bindings/src/events/ReactDOMEventListener.js
// 行号:50-100(React 19.3.0)
export function getEventPriority(domEventName: DOMEventName): EventPriority {
switch (domEventName) {
// 离散事件(DiscreteEventPriority)
// 用户交互,需要立即响应
case 'click':
case 'keydown':
case 'keyup':
case 'input':
case 'change':
case 'submit':
case 'reset':
case 'mousedown':
case 'mouseup':
case 'touchstart':
case 'touchend':
case 'focusin':
case 'focusout':
return DiscreteEventPriority;
// 连续事件(ContinuousEventPriority)
// 频繁触发,可以适当延迟
case 'mousemove':
case 'mouseenter':
case 'mouseleave':
case 'touchmove':
case 'scroll':
case 'wheel':
case 'drag':
case 'dragover':
return ContinuousEventPriority;
// 默认事件(DefaultEventPriority)
// 其他事件
case 'load':
case 'error':
case 'abort':
case 'canplay':
case 'canplaythrough':
case 'durationchange':
case 'emptied':
case 'encrypted':
case 'ended':
case 'loadeddata':
case 'loadedmetadata':
case 'loadstart':
case 'pause':
case 'play':
case 'playing':
case 'progress':
case 'ratechange':
case 'seeked':
case 'seeking':
case 'stalled':
case 'suspend':
case 'timeupdate':
case 'volumechange':
case 'waiting':
return DefaultEventPriority;
default:
return DefaultEventPriority;
}
}优先级对应关系
| 事件类型 | 优先级 | Lane | 说明 | 示例 |
|---|---|---|---|---|
| 离散事件 | DiscreteEventPriority | SyncLane | 最高优先级,同步执行 | click, input, keydown |
| 连续事件 | ContinuousEventPriority | InputContinuousLane | 较高优先级,可批处理 | mousemove, scroll |
| 默认事件 | DefaultEventPriority | DefaultLane | 普通优先级,可延迟 | load, error, media事件 |
13.3.2 与调度系统的集成
事件优先级会影响React的更新调度。
dispatchDiscreteEvent:离散事件处理
javascript
// 文件:packages/react-dom-bindings/src/events/ReactDOMEventListener.js
// 行号:150-200(React 19.3.0)
function dispatchDiscreteEvent(
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
container: EventTarget,
nativeEvent: AnyNativeEvent,
) {
// 获取之前的优先级
const previousPriority = getCurrentUpdatePriority();
const prevTransition = ReactCurrentBatchConfig.transition;
ReactCurrentBatchConfig.transition = null;
try {
// 设置为离散事件优先级(最高)
setCurrentUpdatePriority(DiscreteEventPriority);
// 分发事件
dispatchEvent(domEventName, eventSystemFlags, container, nativeEvent);
} finally {
// 恢复之前的优先级
setCurrentUpdatePriority(previousPriority);
ReactCurrentBatchConfig.transition = prevTransition;
}
}dispatchContinuousEvent:连续事件处理
javascript
// 文件:packages/react-dom-bindings/src/events/ReactDOMEventListener.js
// 行号:250-300(React 19.3.0)
function dispatchContinuousEvent(
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
container: EventTarget,
nativeEvent: AnyNativeEvent,
) {
const previousPriority = getCurrentUpdatePriority();
const prevTransition = ReactCurrentBatchConfig.transition;
ReactCurrentBatchConfig.transition = null;
try {
// 设置为连续事件优先级
setCurrentUpdatePriority(ContinuousEventPriority);
// 分发事件
dispatchEvent(domEventName, eventSystemFlags, container, nativeEvent);
} finally {
setCurrentUpdatePriority(previousPriority);
ReactCurrentBatchConfig.transition = prevTransition;
}
}优先级如何影响更新
当事件处理函数中调用setState时,更新会继承当前的事件优先级:
javascript
function Counter() {
const [count, setCount] = useState(0);
// 离散事件:立即更新
const handleClick = () => {
setCount(c => c + 1); // DiscreteEventPriority -> SyncLane
// 更新会同步执行
};
// 连续事件:可能被批处理
const handleMouseMove = () => {
setCount(c => c + 1); // ContinuousEventPriority -> InputContinuousLane
// 多次调用可能被合并
};
return (
<div>
<button onClick={handleClick}>点击: {count}</button>
<div onMouseMove={handleMouseMove}>移动鼠标: {count}</div>
</div>
);
}事件优先级与Lane的转换
javascript
// 文件:packages/react-reconciler/src/ReactEventPriorities.js
// 行号:50-100(React 19.3.0)
export function eventPriorityToLane(eventPriority: EventPriority): Lane {
switch (eventPriority) {
case DiscreteEventPriority:
return SyncLane;
case ContinuousEventPriority:
return InputContinuousLane;
case DefaultEventPriority:
return DefaultLane;
case IdleEventPriority:
return IdleLane;
default:
return NoLane;
}
}
export function lanesToEventPriority(lanes: Lanes): EventPriority {
const lane = getHighestPriorityLane(lanes);
if (!isHigherEventPriority(DiscreteEventPriority, lane)) {
return DiscreteEventPriority;
}
if (!isHigherEventPriority(ContinuousEventPriority, lane)) {
return ContinuousEventPriority;
}
if (includesNonIdleWork(lane)) {
return DefaultEventPriority;
}
return IdleEventPriority;
}优先级调度流程图
示例:不同优先级的更新行为
javascript
function PriorityDemo() {
const [count, setCount] = useState(0);
const [position, setPosition] = useState({ x: 0, y: 0 });
// 离散事件:高优先级,立即更新
const handleClick = () => {
console.log('点击事件开始');
setCount(c => c + 1);
console.log('点击事件结束');
// 输出:
// 点击事件开始
// 点击事件结束
// (然后立即重新渲染)
};
// 连续事件:较低优先级,可能被节流
const handleMouseMove = (e) => {
setPosition({ x: e.clientX, y: e.clientY });
// 频繁触发,但React会智能批处理
};
// 使用useTransition降低优先级
const [isPending, startTransition] = useTransition();
const handleLowPriorityUpdate = () => {
startTransition(() => {
setCount(c => c + 1);
// 这个更新会被标记为低优先级
// 可以被高优先级更新打断
});
};
return (
<div onMouseMove={handleMouseMove}>
<h1>Count: {count}</h1>
<p>Position: ({position.x}, {position.y})</p>
<button onClick={handleClick}>
高优先级更新
</button>
<button onClick={handleLowPriorityUpdate}>
低优先级更新 {isPending && '(pending)'}
</button>
</div>
);
}批处理与优先级
React 18引入了自动批处理(Automatic Batching),但优先级仍然影响批处理的行为:
javascript
function BatchingDemo() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
const handleClick = () => {
// React 18:自动批处理
setCount(c => c + 1);
setFlag(f => !f);
// 只会触发一次重新渲染
};
const handleClickWithTimeout = () => {
setTimeout(() => {
// React 18:仍然会批处理
setCount(c => c + 1);
setFlag(f => !f);
// 只会触发一次重新渲染
}, 1000);
};
const handleClickWithFetch = async () => {
const data = await fetch('/api/data');
// React 18:异步操作后仍然批处理
setCount(c => c + 1);
setFlag(f => !f);
// 只会触发一次重新渲染
};
console.log('渲染次数:', ++renderCount);
return (
<div>
<p>Count: {count}, Flag: {flag.toString()}</p>
<button onClick={handleClick}>同步批处理</button>
<button onClick={handleClickWithTimeout}>异步批处理</button>
<button onClick={handleClickWithFetch}>Fetch批处理</button>
</div>
);
}事件优先级的实际应用
理解事件优先级对于优化React应用性能至关重要:
javascript
function SearchWithDebounce() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleInputChange = (e) => {
const value = e.target.value;
// 高优先级:立即更新输入框
setQuery(value);
// 低优先级:延迟更新搜索结果
startTransition(() => {
// 模拟搜索
const filtered = performExpensiveSearch(value);
setResults(filtered);
});
};
return (
<div>
<input
type="text"
value={query}
onChange={handleInputChange}
placeholder="搜索..."
/>
{isPending && <div>搜索中...</div>}
<ul>
{results.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
}
function performExpensiveSearch(query) {
// 模拟耗时的搜索操作
const items = Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: `Item ${i}`,
}));
return items.filter(item =>
item.name.toLowerCase().includes(query.toLowerCase())
);
}在这个例子中:
- 输入框更新使用离散事件优先级(DiscreteEventPriority),确保用户输入不会卡顿
- 搜索结果更新使用Transition优先级(较低),可以被输入事件打断
- 这样即使搜索很慢,用户仍然可以流畅地输入
本章小结
本章深入分析了React的事件系统,主要内容包括:
合成事件的设计理念
- 解决浏览器兼容性问题
- 通过事件委托优化性能
- 与React更新机制深度集成
事件委托机制
- React 17+将事件委托到根容器
- 减少内存占用,提高性能
- 支持多个React应用共存
事件注册与触发流程
listenToAllSupportedEvents在根容器注册所有事件dispatchEvent处理原生事件extractEvents构造合成事件并收集监听器processDispatchQueue按顺序执行事件处理函数
事件优先级
- 离散事件(DiscreteEventPriority):最高优先级,同步执行
- 连续事件(ContinuousEventPriority):较高优先级,可批处理
- 默认事件(DefaultEventPriority):普通优先级,可延迟
- 事件优先级与Lane模型的转换
React事件系统是React与用户交互的桥梁,理解其工作原理有助于:
- 正确处理事件冒泡和捕获
- 优化事件处理性能
- 合理使用事件优先级
- 避免常见的事件处理陷阱
思考题
为什么React 17将事件委托从document改为根容器?这对多个React应用共存有什么影响?
React的合成事件与原生事件有什么区别?在什么情况下需要使用
e.nativeEvent?事件优先级如何影响React的更新调度?离散事件和连续事件的处理有什么不同?
如何在React中正确处理事件冒泡?
e.stopPropagation()和e.preventDefault()有什么区别?React 17移除事件池机制的原因是什么?这对开发者有什么影响?
下一章预告
在进阶篇中,我们将学习React Compiler的设计与实现,了解React如何通过编译时优化提升运行时性能。