React之事件机制前言 React v18.3.1 React会所有支持的事件进行事件代理。事件触发时会由触发事件的f
前言
React v18.3.1
本来只想整理listenToAllSupportedEvents()的,但单独整理listenToAllSupportedEvents()给人一种电影放了一半你才落座的感觉。干脆整理React的事件机制,直接贯通🌟~
React的事件机制是什么?
React会所有支持的事件进行#事件代理。事件触发时会由触发事件的fiberNode向上收集捕获->冒泡(#事件流)事件、创建合成事件对象。收集完成后逐一执行收集的事件。
简单来说,React事件机制就是事件代理、事件收集然后同类事件统一执行的机制。
为什么要自己实现一套事件机制?
- 拥有onClick这样处理函数的fiberNode生成时,其DOM节点可能还未创建,所以onClick无法绑定到真实的DOM节点
- 进行事件代理,不需监听每个元素的事件,减少了事件监听器的创建,节省内存
- 使用合成事件对象磨平了浏览器差异,比如:取消冒泡
nativeEvent.stopPropagation()
或nativeEvent.cancelBubble = true
,在React只需使用syntheticEvent.stopPropagation()
React事件机制的实现细节
1. React事件分类
这里的事件分类是指对React事件进行分类,React事件会依赖一个或多个原生事件。对于使用者来说无需过多关心原生事件,直接使用React事件即可,比如:对于<input type="text" />、<input type="radio" />、<input type="checkbox" />统一使用onChange
收集即可。
React根据React事件依赖原生事件的数量以及它们对应的操作将事件分为了5类,并通过各自的插件注册:
- 输入前事件(BeforeInputEventPlugin):onBeforeInput、onCompositionEnd...
- 表单变化事件(ChangeEventPlugin):onChange
- 鼠标移入移除事件(EnterLeaveEventPlugin):onMouseEnter、onPointerEnter...
- 选择事件(SelectEventPlugin):onSelect
- 简单事件(SimpleEventPlugin):只依赖一个原生事件的React事件,如onClick、onKeyPress...
2. 收集支持代理、不支持代理的原生事件
由于React需要对所有事件进行代理,所以需要知道所有支持的原生事件。
在注册React事件时会将其依赖的原生事件添加到allNativeEvents集合
中,并通过registrationNameDependencies对象
维护React事件与其依赖的原生事件的关系。
对于不支持代理的事件会保存在nonDelegatedEvents集合
中。在#commitWork阶段(留待源码学习完毕后归纳)给相应DOM元素添加属性时,监听nonDelegatedEvents。
// 注册React事件
SimpleEventPlugin.registerEvents();
EnterLeaveEventPlugin.registerEvents();
ChangeEventPlugin.registerEvents();
SelectEventPlugin.registerEvents();
BeforeInputEventPlugin.registerEvents();
// 所有原生事件
const allNativeEvents = new Set<DOMEventName>([
'click',
'dbclick',
'focus',
....
]);
const registrationNameDependencies = {
onClick: ['click'],
onChange: [
'change',
'click',
'focusin',
'focusout',
'input',
'keydown',
'keyup',
'selectionchange'
],
...
}
// 原生媒体事件
const mediaEventTypes: Array<DOMEventName> = [
'pause',
'play',
'progress',
...
];
// 不支持冒泡的原生事件
const nonDelegatedEvents = new Set<DOMEventName>([cancel',
'close',
'invalid',
'load',
'scroll',
'toggle',
...mediaEventTypes
]);
3. 事件代理
listenToAllSupportedEvents
- 在rootContainerElement上监听所有原生事件(不包括selectionchange):对于支持冒泡的事件会注册捕获、冒泡阶段的事件;对于不支持冒泡的事件只会注册捕获阶段的事件
- 获取document并监听selectionchange事件,在document上添加表示已进行事件委托的属性:listeningMarker
不管是rootContainerElement还是document,进行过事件代理后都会添加listeningMarker属性,表示已进行了事件代理,不会重复代理。
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);
}
});
// 获取document
const ownerDocument =
(rootContainerElement: any).nodeType === DOCUMENT_NODE
? rootContainerElement
: (rootContainerElement: any).ownerDocument;
if (ownerDocument !== null) {
if (!(ownerDocument: any)[listeningMarker]) {
// 在document上监听selectchange事件
(ownerDocument: any)[listeningMarker] = true;
listenToNativeEvent('selectionchange', false, ownerDocument);
}
}
}
}
listenToNativeEvent
listenToNativeEvent()用于监听原生事件。它有3个参数:
- domEventName: 原生事件名
- isCapturePhaseListener: 是否捕获阶段
- target: 绑定事件的目标元素
listenToNativeEvent()内部会调用其他方法(因为本身对源码还不是很了解,所以这里不继续拓展了。等源码学习完毕再回头整理),这里简单概括下:
- 根据事件名称获取事件优先级
- 根据事件优先级获取对应的dispatchEvent()
- 将dispatchEvent()作为事件监听器绑定到目标元素上
4. 事件触发
以SimpleEventPlugin为例,当代理的事件监听器((dispatchEvent())触发时,React会做如下3件事:
边看边写给看晕了,来张图总结一下
可能有理解错误的地方,等坚持学下去、对源码的理解更深后再做整理。
待办
- 整理事件委托
- 整理事件流
- 整理commitWork阶段
- 整理accumulateSinglePhaseListeners()
- 整理extractEvents()
- 整理processDispatchQueue()
转载自:https://juejin.cn/post/7405778015189368869