动机
抹平浏览器兼容性
- 给事件添加优先级,便于调度
- 减少事件绑定(相比vue,vue在事件绑定的地方也有优化(事件缓存,事件绑定的优化),但是仍然需要绑定事件),但是也会产生一个新问题,不管有没有事件处理器,都会需要反向遍历fiber树,并且一般情况下会遍历两遍
实现思路
- 根据事件不同获取不同的优先级的事件监听器(dispatchEvent)
- 在root元素上绑定对应的事件监听器(dispachEvent),包括捕获阶段和冒泡阶段
- 当事件发生的时候,被注册的监听器拦截到
- 根据事件的target属性,获取事件发生的真实dom对象(target),根据dom,获取对应的fiber
- 根据获取的fiber,根据return属性,向上遍历收集fiber上声明的事件处理函数
- 根据事件处理函数列表,并结合是冒泡还是捕获阶段,正向或者反向执行事件函数
- 事件函数的执行不是直接调用事件处理函数,而是通过自定义事件做的。
相关代码
function createRoot(container, options){
var root = createContainer(container, ConcurrentRoot, null, isStrictMode, concurrentUpdatesByDefaultOverride, identifierPrefix, onRecoverableError);
markContainerAsRoot(root.current, container);
var rootContainerElement = container.nodeType === COMMENT_NODE ? container.parentNode : container;
listenToAllSupportedEvents(rootContainerElement);
return new ReactDOMRoot(root);
}
function listenToAllSupportedEvents(rootContainerElement) {
if (!rootContainerElement[listeningMarker]) {
rootContainerElement[listeningMarker] = true;
allNativeEvents.forEach(function (domEventName) {
if (domEventName !== 'selectionchange') {
if (!nonDelegatedEvents.has(domEventName)) {
listenToNativeEvent(domEventName, false, rootContainerElement);
}
listenToNativeEvent(domEventName, true, rootContainerElement);
}
});
var ownerDocument = rootContainerElement.nodeType === DOCUMENT_NODE ? rootContainerElement : rootContainerElement.ownerDocument;
if (ownerDocument !== null) {
if (!ownerDocument[listeningMarker]) {
ownerDocument[listeningMarker] = true;
listenToNativeEvent('selectionchange', false, ownerDocument);
}
}
}
}
function listenToNativeEvent(domEventName, isCapturePhaseListener, target) {
var eventSystemFlags = 0;
if (isCapturePhaseListener) {
eventSystemFlags |= IS_CAPTURE_PHASE;
}
addTrappedEventListener(target, domEventName, eventSystemFlags, isCapturePhaseListener);
}
function addTrappedEventListener(targetContainer, domEventName, eventSystemFlags, isCapturePhaseListener, isDeferredListenerForLegacyFBSupport) {
var listener = createEventListenerWrapperWithPriority(targetContainer, domEventName, eventSystemFlags);
var isPassiveListener = undefined;
if (passiveBrowserEventsSupported) {
if (domEventName === 'touchstart' || domEventName === 'touchmove' || domEventName === 'wheel') {
isPassiveListener = true;
}
}
targetContainer = targetContainer;
var unsubscribeListener;
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);
}
}
}
// 根据事件的优先级,获取对应的包装器,实际上就是dispatch方法
function createEventListenerWrapperWithPriority(targetContainer, domEventName, eventSystemFlags) {
var eventPriority = getEventPriority(domEventName)
var 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 getEventPriority(domEventName) {
switch (domEventName) {
case 'cancel':
...
case 'beforeinput':
return DiscreteEventPriority;
case 'drag':
...
case 'pointerleave':
return ContinuousEventPriority;
case 'message':
{
default:
return DefaultEventPriority;
}
}
default:
return DefaultEventPriority;
}
}
function addEventCaptureListener(target, eventType, listener) {
target.addEventListener(eventType, listener, true);
return listener;
}
function addEventBubbleListener(target, eventType, listener) {
target.addEventListener(eventType, listener, false);
return listener;
}
function dispatchEventWithEnableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay(domEventName, eventSystemFlags, targetContainer, nativeEvent) {
if (blockedOn === null) {
dispatchEventForPluginEventSystem(domEventName, eventSystemFlags, nativeEvent, return_targetInst, targetContainer);
clearIfContinuousEvent(domEventName, nativeEvent);
return;
}
}
function dispatchEventForPluginEventSystem(domEventName, eventSystemFlags, nativeEvent, targetInst, targetContainer) {
...
batchedUpdates(function () {
return dispatchEventsForPlugins(domEventName, eventSystemFlags, nativeEvent, ancestorInst);
});
}
function batchedUpdates(fn, a, b) {
if (isInsideEventHandler) {
return fn(a, b);
}
isInsideEventHandler = true;
try {
return batchedUpdatesImpl(fn, a, b);
} finally {
isInsideEventHandler = false;
finishEventHandler();
}
}
function dispatchEventsForPlugins(domEventName, eventSystemFlags, nativeEvent, targetInst, targetContainer) {
var nativeEventTarget = getEventTarget(nativeEvent);
var dispatchQueue = [];
extractEvents$5(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags);
processDispatchQueue(dispatchQueue, eventSystemFlags);
}
function processDispatchQueue(dispatchQueue, eventSystemFlags) {
var inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0
for (var i = 0
var _dispatchQueue$i = dispatchQueue[i],
event = _dispatchQueue$i.event,
listeners = _dispatchQueue$i.listeners
// 执行事件处理器(列表)
processDispatchQueueItemsInOrder(event, listeners, inCapturePhase)
} // This would be a good time to rethrow if any of the event handlers threw.
rethrowCaughtError()
}
function processDispatchQueueItemsInOrder(event, dispatchListeners, inCapturePhase) {
var previousInstance
// 如果是捕获阶段,反向遍历执行事件
if (inCapturePhase) {
for (var i = dispatchListeners.length - 1
var _dispatchListeners$i = dispatchListeners[i],
instance = _dispatchListeners$i.instance,
currentTarget = _dispatchListeners$i.currentTarget,
listener = _dispatchListeners$i.listener
if (instance !== previousInstance && event.isPropagationStopped()) {
return
}
executeDispatch(event, listener, currentTarget)
previousInstance = instance
}
} else {
// 如果是冒泡阶段,正向执行事件
for (var _i = 0
var _dispatchListeners$_i = dispatchListeners[_i],
_instance = _dispatchListeners$_i.instance,
_currentTarget = _dispatchListeners$_i.currentTarget,
_listener = _dispatchListeners$_i.listener
if (_instance !== previousInstance && event.isPropagationStopped()) {
return
}
executeDispatch(event, _listener, _currentTarget)
previousInstance = _instance
}
}
}
function executeDispatch(event, listener, currentTarget) {
var type = event.type || 'unknown-event'
event.currentTarget = currentTarget
// 执行事件处理器
invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event)
event.currentTarget = null
}
function invokeGuardedCallbackDev(name, func, context, a, b, c, d, e, f) {
var evt = document.createEvent('Event');
var didCall = false;
var didError = true;
var windowEvent = window.event;
var windowEventDescriptor = Object.getOwnPropertyDescriptor(window, 'event');
function restoreAfterDispatch() {
fakeNode.removeEventListener(evtType, callCallback, false);
if (typeof window.event !== 'undefined' && window.hasOwnProperty('event')) {
window.event = windowEvent;
}
}
var funcArgs = Array.prototype.slice.call(arguments, 3);
function callCallback() {
didCall = true;
restoreAfterDispatch();
func.apply(context, funcArgs);
didError = false;
}
var error;
var didSetError = false;
var isCrossOriginError = false;
function handleWindowError(event) {
error = event.error;
didSetError = true;
if (error === null && event.colno === 0 && event.lineno === 0) {
isCrossOriginError = true;
}
if (event.defaultPrevented) {
if (error != null && typeof error === 'object') {
try {
error._suppressLogging = true;
} catch (inner) {
}
}
}
}
var evtType = "react-" + (name ? name : 'invokeguardedcallback');
window.addEventListener('error', handleWindowError);
fakeNode.addEventListener(evtType, callCallback, false);
evt.initEvent(evtType, false, false);
fakeNode.dispatchEvent(evt);
if (windowEventDescriptor) {
Object.defineProperty(window, 'event', windowEventDescriptor);
}
if (didCall && didError) {
if (!didSetError) {
error = new Error('An error was thrown inside one of your components, but React ' + "doesn't know what it was. This is likely due to browser " + 'flakiness. React does its best to preserve the "Pause on ' + 'exceptions" behavior of the DevTools, which requires some ' + "DEV-mode only tricks. It's possible that these don't work in " + 'your browser. Try triggering the error in production mode, ' + 'or switching to a modern browser. If you suspect that this is ' + 'actually an issue with React, please file an issue.');
} else if (isCrossOriginError) {
error = new Error("A cross-origin error was thrown. React doesn't have access to " + 'the actual error object in development. ' + 'See https://reactjs.org/link/crossorigin-error for more information.');
}
this.onError(error);
}
window.removeEventListener('error', handleWindowError);
if (!didCall) {
restoreAfterDispatch();
return invokeGuardedCallbackProd.apply(this, arguments);
}
};
}
}
Debug
TODO
- 为什么不直接执行事件函数而是通过触发自定义事件的方式执行