首先要知道,通过react注册的事件大部分并没有被注册到本身节点上,而是统一的注册到了document节点上,目标节点的事件被触时,该事件会冒泡到document上,注册到document的事件回调函数最终会执行到dispatchEvent函数,dispatchEvent函数主要进行事件分发,也就是执行需要执行的回调。
第一个问题:事件处理函数是如何存储的,存储到了什么地方?
事件处理函数是如何存储的这个答案尚未得出,但是从第二个问题的答案可以看出,事件处理函数被存储到了currentTarget[internalEventHandlerListenersKey]上,currentTarget多数情况下都是document节点。
第二个问题:事件被触发时,如何拿到事件目标节点到document节点链路中所有与该事件类型有关的回调函数的?
当事件被触发时,事件会冒泡到document上,document会存在该事件类型的回调,并且只有一个该事件类型的回调。当该回调被触发时,会执行到一个名为dispatchEvent的函数,该函数主要作用是事件分发,或者说是执行所有需要执行的回调。
dispatchEvent:
function dispatchEvent(domEventName: DOMEventName, // 事件名eventSystemFlags: EventSystemFlags, // 事件系统标志位targetContainer: EventTarget, // 事件回调绑定的目标节点nativeEvent: AnyNativeEvent, // 原生的事件对象) {...省略const blockedOn = attemptToDispatchEvent(domEventName,eventSystemFlags,targetContainer,nativeEvent,);...省略}
可以看到 dispatchEvent内部实际调用了attemptToDispatchEvent来进行事件分发的,接下来看attemptToDispatchEvent的代码
attemptToDispatchEvent:
function attemptToDispatchEvent(domEventName: DOMEventName,eventSystemFlags: EventSystemFlags,targetContainer: EventTarget,nativeEvent: AnyNativeEvent,
) {const nativeEventTarget = getEventTarget(nativeEvent); // 真正触发click事件的dom节点 例如 button#btnlet targetInst = getClosestInstanceFromNode(nativeEventTarget);// 返回目标节点对应的Fiber...省略dispatchEventForPluginEventSystem(domEventName,eventSystemFlags,nativeEvent,targetInst, // 极有可能为nulltargetContainer,);
}
在attemptToDispatchEvent内部又调用了dispatchEventForPluginEventSystem函数,继续向下看
dispatchEventForPluginEventSystem:
function dispatchEventForPluginEventSystem(domEventName: DOMEventName,eventSystemFlags: EventSystemFlags,nativeEvent: AnyNativeEvent,targetInst: null | Fiber,targetContainer: EventTarget,
) {...继续省略dispatchEventsForPlugins(domEventName,eventSystemFlags,nativeEvent,ancestorInst,targetContainer,),
}
继续向下走,去看dispatchEventsForPlugins函数的执行逻辑
dispatchEventsForPlugins:
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和processDispatchQueue这两个函数。extractEvents这个函数主要作用是收集从触发事件的节点到document节点链路上所有的事件及回调函数,已{event: event, listeners: listeners}的数据结构存入dispatchQueue这个数组中,而processDispatchQueue这个函数则是根据事件类型, 执行dispatchQueue数组中对应事件类型的回调函数,也就是遍历执行listeners中的回调。接下来看一下extractEvents这个函数是怎么收集listeners的。
extractEvents:
function extractEvents(dispatchQueue: DispatchQueue,domEventName: DOMEventName,targetInst: null | Fiber,nativeEvent: AnyNativeEvent,nativeEventTarget: null | EventTarget,eventSystemFlags: EventSystemFlags,targetContainer: EventTarget,
) {// 我们主要看这个函数的实现SimpleEventPlugin.extractEvents(dispatchQueue,domEventName,targetInst,nativeEvent,nativeEventTarget,eventSystemFlags,targetContainer,);
}
SimpleEventPlugin.extractEvents = function extractEvents(dispatchQueue: DispatchQueue,domEventName: DOMEventName,targetInst: null | Fiber,nativeEvent: AnyNativeEvent,nativeEventTarget: null | EventTarget,eventSystemFlags: EventSystemFlags,targetContainer: EventTarget,
) {...省略// 通过SyntheticEvent基类 创建事件对象const event = new SyntheticEvent(reactName,reactEventType,null,nativeEvent,nativeEventTarget,EventInterface,);...省略accumulateSinglePhaseListeners(targetInst,dispatchQueue,event,inCapturePhase,accumulateTargetOnly,);
}
接下里走到了accumulateSinglePhaseListeners这个函数这里,这个函数是真正进行listeners收集的地方
// 为了更容易理解收集的过程,省略了此函数内部的部分逻辑
function accumulateSinglePhaseListeners(targetFiber: Fiber | null,dispatchQueue: DispatchQueue,event: ReactSyntheticEvent,inCapturePhase: boolean,accumulateTargetOnly: boolean,
) {const listeners: Array<DispatchListener> = [];let instance = targetFiber; // target对应的Fiberconst targetType = event.nativeEvent.type;while (instance !== null) {const currentTarget = stateNode;// 实际就是currentTarget[internalEventHandlerListenersKey] 该值是一个Set对象 存储该节点上的所有的事件回调const eventHandlerlisteners = getEventHandlerListeners(currentTarget); if (eventHandlerlisteners !== null) {const eventHandlerlistenersArr = Array.from(eventHandlerlisteners); // 将Set对象转化为数组// 对数组进行遍历for (let i = 0; i < eventHandlerlistenersArr.length; i++) {const {callback, type} = eventHandlerlistenersArr[i];// 当type为当前触发的事件类型时,才进行回调与事件对象的收集if (type === targetType) {// 如果存储在Set对象上的事件与targetType一样,则把其回调与Fiber、dom节点放入listeners数组中listeners.push({instance, callback, currentTarget});}}}// instance指向的是父节点,从当前节点,一层一层向上递归查找所有该类型的事件,直到document节点instance = instance.return;}// 当事件类型与当前触发的事件类型一样时,才会进行push操作,因此通过判断listeners.length !== 0可以将其他类型的事件过滤掉if (listeners.length !== 0) {// 将回调数组与事件回调放入dispatchQueue数组中dispatchQueue.push({event, listeners});}
}
到目前为止,我们已经收集到与该事件相关的事件对象以及回调数组,接下来就是执行这写回调了。具体看processDispatchQueue函数
processDispatchQueue:
function processDispatchQueue(dispatchQueue: DispatchQueue,eventSystemFlags: EventSystemFlags,
): void {for (let i = 0; i < dispatchQueue.length; i++) {const {event, listeners} = dispatchQueue[i];// 遍历dispatchQueue,具体的回调执行在processDispatchQueueItemsInOrder函数processDispatchQueueItemsInOrder(event, listeners, inCapturePhase);}
}
function processDispatchQueueItemsInOrder(event: ReactSyntheticEvent,dispatchListeners: Array<DispatchListener>,inCapturePhase: boolean,
): void {for (let i = 0; i < dispatchListeners.length; i++) {const {instance, currentTarget, listener} = dispatchListeners[i];// 当执行了event.stopPropagetion()时, event.isPropagationStopped()的返回值就会为true,则停止后续回调的执行,实现阻止冒泡的效果if (instance !== previousInstance && event.isPropagationStopped()) {return;}// 遍历回调数组,即listeners数组,拿到所有的listener,执行回调executeDispatch(event, listener, currentTarget);}
}
function executeDispatch(event: ReactSyntheticEvent,listener: Function,currentTarget: EventTarget,
): void {const type = event.type || 'unknown-event';event.currentTarget = currentTarget;invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event); // 这行代码其实就是 listener(event)event.currentTarget = null;
}