React 为我们提供了一套虚拟的事件系统,并且是遵循W3C规范来定义这些事件。在 React事件介绍 中介绍了合成事件对象以及为什么提供合成事件对象,主要原因是想抹平不同浏览器之间的兼容差异,提供一个可以跨平台的事件系统,并且能做优化和能干预事件的分发,为此就需要提供能在不同浏览器下一致的事件系统。
事件系统由事件注册、事件绑定、事件触发三个环节组成,事件注册负责注册事件形成映射关系,事件绑定在commit阶段会给原生节点上注册监听器,事件触发负责形成事件对象和收集事件,最终释放事件池。
⚠️: 章节内容对应的是React18版本,和旧版本可能存在差异,具体差异不会讲解。
事件注册
react 中,组件注册的事件,往往不是真正的事件,为什么怎么说,因为在react中注册事件的方式有两种,一种是原生事件,另外就是注册合成事件,并且合成事件最终也是注册在根容器上的,然后通过冒泡的形式最终触发。
在新的事件系统中,createRoot方法一次性往容器上注册所有支持的的事件(listenToAllSupportedEvents):
function createRoot (container, options) { ...省略 listenToAllSupportedEvents(rootContainerElement); }
listenToAllSupportedEvents就是注册事件的关键方法,来自packages/react-dom-bindings/src/events/DOMPluginEventSystem模块(react-dom-bindings下的所有模块都是和dom相关的),先是将事件插件全部加载进来(tu),然后才是执行listenToAllSupportedEvents方法
import * as SimpleEventPlugin from './plugins/SimpleEventPlugin'; import * as EnterLeaveEventPlugin from './plugins/EnterLeaveEventPlugin'; import * as ChangeEventPlugin from './plugins/ChangeEventPlugin'; import * as SelectEventPlugin from './plugins/SelectEventPlugin'; import * as BeforeInputEventPlugin from './plugins/BeforeInputEventPlugin'; // TODO: remove top-level side effect. SimpleEventPlugin.registerEvents(); EnterLeaveEventPlugin.registerEvents(); ChangeEventPlugin.registerEvents(); SelectEventPlugin.registerEvents(); BeforeInputEventPlugin.registerEvents(); // 省略部分..... export function listenToAllSupportedEvents(rootContainerElement: EventTarget) { if (!(rootContainerElement: any)[listeningMarker]) { (rootContainerElement: any)[listeningMarker] = true; allNativeEvents.forEach(domEventName => { // We handle selectionchange separately because it // doesn't bubble and needs to be on the document. if (domEventName !== 'selectionchange') { if (!nonDelegatedEvents.has(domEventName)) { listenToNativeEvent(domEventName, false, rootContainerElement); } listenToNativeEvent(domEventName, true, rootContainerElement); } }); const ownerDocument = (rootContainerElement: any).nodeType === DOCUMENT_NODE ? rootContainerElement : (rootContainerElement: any).ownerDocument; if (ownerDocument !== null) { // The selectionchange event also needs deduplication // but it is attached to the document. if (!(ownerDocument: any)[listeningMarker]) { (ownerDocument: any)[listeningMarker] = true; listenToNativeEvent('selectionchange', false, ownerDocument); } } } }
注册完不同的事件插件后,就初始化好了一些全局变量。第一个是registrationNameDependencies,它包含的是React支持的事件类型,只有组件上的prop在这个对象内才会被当作事件处理。
{ onBlur: ['blur'], onClick: ['click'], onClickCapture: ['click'], onChange: ['blur', 'change', 'click', 'focus', 'input', 'keydown', 'keyup', 'selectionchange'], ... } : Object
第二个是allNativeEvents,集合了所有定义的事件类型,listenToAllSupportedEvents方法中遍历的集合就是初始化后的allNativeEvents。
const allNativeEvents = ['click', 'change', ...]: Set
第三是topLevelEventsToReactNames,也是集合类型,实现了原生事件和合成事件的映射,事件触发阶段,会根据原生事件类型判断是否存在合成事件类型,才进行下一执行。
const topLevelEventsToReactNames = { "click" => "onClick" ...... }: Map
1. listenToAllSupportedEvents
事件插件加载完成全局变量的初始化,到这里就可以开始注册事件了。listenToAllSupportedEvents遍历事件类型集合allNativeEvents,将事件类型注册到容器上。
export function listenToAllSupportedEvents(rootContainerElement: EventTarget) { // 判断根容器上是否有listeningMarker属性,有则说明已经事件已经注册过了 if (!(rootContainerElement: any)[listeningMarker]) { (rootContainerElement: any)[listeningMarker] = true; allNativeEvents.forEach(domEventName => { // selectionchange事件不注册在容器上 if (domEventName !== 'selectionchange') { // ...重点 } }); const ownerDocument = (rootContainerElement: any).nodeType === DOCUMENT_NODE ? rootContainerElement : (rootContainerElement: any).ownerDocument; if (ownerDocument !== null) { // The selectionchange event also needs deduplication // but it is attached to the document. if (!(ownerDocument: any)[listeningMarker]) { (ownerDocument: any)[listeningMarker] = true; // 注册selectionchange事件 listenToNativeEvent('selectionchange', false, ownerDocument); } } } }
注册事件前会先判断是否已经注册过了,然后便利allNativeEvents集合,判断事件不是selectionchange才注册,当所有的事件注册结束后,才将selectionchange注册到ownerDocument上。
if (!nonDelegatedEvents.has(domEventName)) { listenToNativeEvent(domEventName, false, rootContainerElement); } listenToNativeEvent(domEventName, true, rootContainerElement);
这里将事件名,容器和一个判断冒泡或捕获的布尔值到listentToNativeEvent。
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, isDeferredListenerForLegacyFBSupport?: boolean, ) { let listener = createEventListenerWrapperWithPriority( targetContainer, domEventName, eventSystemFlags, ); // 省略... if (isCapturePhaseListener) { if (isPassiveListener !== undefined) { unsubscribeListener = addEventCaptureListenerWithPassiveFlag( targetContainer, domEventName, listener, isPassiveListener, ); } else { unsubscribeListener = addEventCaptureListener( targetContainer, domEventName, listener, ); } } else { if (isPassiveListener !== undefined) { unsubscribeListener = addEventBubbleListenerWithPassiveFlag( targetContainer, domEventName, listener, isPassiveListener, ); } else { unsubscribeListener = addEventBubbleListener( targetContainer, domEventName, listener, ); } } } function createEventListenerWrapperWithPriority( targetContainer: EventTarget, domEventName: DOMEventName, eventSystemFlags: EventSystemFlags, ): Function { const eventPriority = getEventPriority(domEventName); // 获取事件的优先级 let listenerWrapper; switch (eventPriority) { case DiscreteEventPriority: listenerWrapper = dispatchDiscreteEvent; break; case ContinuousEventPriority: listenerWrapper = dispatchContinuousEvent; break; case DefaultEventPriority: default: listenerWrapper = dispatchEvent; break; } return listenerWrapper.bind( null, domEventName, eventSystemFlags, targetContainer, ); } function dispatchDiscreteEvent(){} function dispatchContinuousEvent(){} function dispatchEvent(){} // 冒泡 function addEventBubbleListenerWithPassiveFlag(...) { target.addEventListener(eventType, listener, { passive, }); } function addEventBubbleListener(...) { target.addEventListener(eventType, listener, false); } // 捕获 function addEventCaptureListenerWithPassiveFlag(...) { target.addEventListener(eventType, listener, { capture: true, passive, }); return listener; } function addEventCaptureListener( target: EventTarget, eventType: string, listener: Function, ){ target.addEventListener(eventType, listener, true); return listener; }
可以看到实际是addTrappedEventListener做了计算,先是调用 createEventListenerWrapperWithPriority创建了事件listener,然后将listener作为addEventListener的第二个参数,这里要始终记住target永远是容器。
事件绑定
事件绑定是发生在reconiler中的commit阶段,当实例创建成功被推到了父节点和保存在fiber上的stateNode上后,就开始执行处理props信息。
function completeWork(current, workInProgress, renderLanes) { //...省略 switch (workInProgress.tag) { case HostComponent: { if(finalizeInitialChildren(...)){ // ... } } } } function finalizeInitialChildren(...){ setInitialProperties(domElement, type, props); } function setInitialProperties(...){ swith(tag){ // ... case 'source': listenToNonDelegatedEvent('error', domElement); // ... } // ... }
packages/react-dom-bindings/src/client/ReactDOMHostConfig.js模块中的finalizeInitialChildren只是做了一层代理,最终是在setInitialProperties方法,区分不同的元素类型执行了listenToNonDelegatedEvent方法,在元素本身注册事件,在这里会将在元素上定义的事件如果是可以冒泡的,那么监听的原生事件就是一个空函数,另外还有一些是不能将事件委托到容器上的,也要给元素上监听事件,不过,我们定义的事件函数,最终会被保留在定义事件的元素上,这在后面的事件触发上起到作用。
事件触发
当事件触发,根据事件模型,事件最终会冒泡到容器上被接收,也就是前面的事件注册中的listener,这个工程首先是开始形成事件对象(packages/react-dom-bindings/src/events/plugins/SimpleEventPlugin.js);
标签:function,...,系统,listener,事件,domEventName,随笔,rootContainerElement From: https://www.cnblogs.com/goather/p/17364030.html