react
合成事件是react
模拟原生dom
事件所有能力得一个事件对象。拥有与浏览器原生事件相同的接口。
本篇文章基于V17.0.3
来研究react
的合成事件
都知道react16
是把事件绑定在document
上,自己实现了一套事件机制,react17
的区别是把事件绑定到根节点
从react
将jsx
解析成element
的格式。带有事件的情况会解析成如下所示:
props
解析携带onClick
{$$typeof: Symbol(react.element), type: 'div', key: null, ref: null, props: {…}, …}
$$typeof: Symbol(react.element)
key: null
props: {className: 'border', children: {…}, onClick: ƒ}
ref: null
type: "div"
_owner: null
_store: {validated: false}
_self: undefined
_source: {fileName: 'C:\\workspace\\study\\react-study\\hooks\\src\\index.js', lineNumber: 11, columnNumber: 3}
[[Prototype]]: Object
在 beginwork
阶段解析上述数据为fiber
结构 memoizedProps
和pendingProps
在函数createRootImpl
调用listenToAllSupportedEvents
监听所有的支持的事件类型绑定事件类型
// 循环遍历所有的浏览器事件枚举。注册添加监听事件,监听root的事件,
allNativeEvents.forEach(function (domEventName) {
if (!nonDelegatedEvents.has(domEventName)) {
listenToNativeEvent(domEventName, false, rootContainerElement, null);
}
listenToNativeEvent(domEventName, true, rootContainerElement, null);
});
调用listenToNativeEvent
下的addTrappedEventListener
为每一种事件种类添加派发函数
function addTrappedEventListener(targetContainer, domEventName, eventSystemFlags, isCapturePhaseListener, isDeferredListenerForLegacyFBSupport) {
// 创建派发函数
var listener = createEventListenerWrapperWithPriority(targetContainer, domEventName, eventSystemFlags); // If passive option is not supported, then the event will be
// active and not passive.
var isPassiveListener = undefined;
if (passiveBrowserEventsSupported) {
if (domEventName === 'touchstart' || domEventName === 'touchmove' || domEventName === 'wheel') {
isPassiveListener = true;
}
}
targetContainer = targetContainer;
var unsubscribeListener; // When legacyFBSupport is enabled, it's for when we
// 添加root监听器
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);
}
}
}
addTrappedEventListener
做了两件事,一件是创建派发函数,一件是往root接点添加原生事件监听器
派发函数createEventListenerWrapperWithPriority
通过bind
传递domEventName
、eventSystemFlags
、targetContainer
。注册事件时,targetContainer
是root
function createEventListenerWrapperWithPriority(targetContainer, domEventName, eventSystemFlags) {
var eventPriority = getEventPriorityForPluginSystem(domEventName);
var listenerWrapper;
switch (eventPriority) {
case DiscreteEvent:
listenerWrapper = dispatchDiscreteEvent;
break;
case UserBlockingEvent:
listenerWrapper = dispatchUserBlockingUpdate;
break;
case ContinuousEvent:
default:
listenerWrapper = dispatchEvent;
break;
}
return listenerWrapper.bind(null, domEventName, eventSystemFlags, targetContainer);
}
添加监听函数addEventCaptureListener
,为root
添加上event
监听事件。监听函数为dispatchEvent
、dispatchDiscreteEvent
、dispatchUserBlockingUpdate
function addEventCaptureListener(target, eventType, listener) {
target.addEventListener(eventType, listener, true);
return listener;
}
在render
开始阶段,对事件处理并添加到root
上添加监听事件,通过root
去捕获页面的事件
root
上有事件监听,当我们点击事件时就触发了绑定在root
上的handler
,执行react
的派发函数
调用顺序dispatchEvent
->attemptToDispatchEvent
->dispatchEventForPluginEventSystem
->dispatchEventsForPlugins
->extractEvents
->processDispatchQueue
->processDispatchQueueItemsInOrder
->executeDispatch
attemptToDispatchEvent
通过原生事件的event.taget|| event.srcElement
获取到当前点击的dom
元素
function attemptToDispatchEvent(domEventName, eventSystemFlags, targetContainer, nativeEvent) {
// 获取当前点击的元素,便于节点自底向上查找
var nativeEventTarget = getEventTarget(nativeEvent);
// 找到fiber
var targetInst = getClosestInstanceFromNode(nativeEventTarget);
if (targetInst !== null) {
var nearestMounted = getNearestMountedFiber(targetInst);
if (nearestMounted === null) {
// This tree has been unmounted already. Dispatch without a target.
targetInst = null;
} else {
var tag = nearestMounted.tag;
if (tag === SuspenseComponent) {
var instance = getSuspenseInstanceFromFiber(nearestMounted);
if (instance !== null) {
return instance;
}
targetInst = null;
} else if (tag === HostRoot) {
var root = nearestMounted.stateNode;
if (root.hydrate) {
return getContainerFromFiber(nearestMounted);
}
targetInst = null;
} else if (nearestMounted !== targetInst) {
targetInst = null;
}
}
}
dispatchEventForPluginEventSystem(domEventName, eventSystemFlags, nativeEvent, targetInst, targetContainer); // We're not blocked on anything.
return null;
}
react
得stateNode
在渲染之初就有标记每个真实接点对应得fiber
,上诉我们获取到了dom
节点,可以通过这个标记找到该节点对应得fiber
,生成时机在生成真实dom
时。会往stateNode
上添加一个唯一值由react
+随机数生成。且随机数全局定义。
var targetInst = targetNode[internalInstanceKey];
if (targetInst) {
// Don't return HostRoot or SuspenseComponent here.
return targetInst;
}
dispatchEventsForPlugins
根据fiber
顺序执行extractEvents
和processDispatchQueue
function dispatchEventsForPlugins(domEventName, eventSystemFlags, nativeEvent, targetInst, targetContainer) {
// 获取原生dom
var nativeEventTarget = getEventTarget(nativeEvent);
var dispatchQueue = [];
extractEvents$5(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags);
processDispatchQueue(dispatchQueue, eventSystemFlags);
}
extractEvents$5
根据domEvent
查询react
事件名。将fiber
上的事件比如:onClick
绑定的函数推入dispatchQueue
,调用accumulateSinglePhaseListeners
->getListener
,根据fiber的return自底向上查找对应事件名,getListener
根据props
的react
事件名读取,dispatchQueue
结构是数组对象,对象包含event
和listener
,用于后续实际触发执行
// 自定义事件类型
switch (domEventName) {
case 'pointerup':
SyntheticEventCtor = SyntheticPointerEvent;
break;
}
//
var _listeners = accumulateSinglePhaseListeners(targetInst, reactName, nativeEvent.type, inCapturePhase, accumulateTargetOnly);
if (_listeners.length > 0) {
// Intentionally create event lazily.
var _event = new SyntheticEventCtor(reactName, reactEventType, null, nativeEvent, nativeEventTarget);
dispatchQueue.push({
event: _event,
listeners: _listeners
});
}
function accumulateSinglePhaseListeners(targetFiber, reactName, nativeEventType, inCapturePhase, accumulateTargetOnly) {
var captureName = reactName !== null ? reactName + 'Capture' : null;
var reactEventName = inCapturePhase ? captureName : reactName;
var listeners = [];
var instance = targetFiber;
var lastHostComponent = null; // Accumulate all instances and listeners via the target -> root path.
// 自顶向下查找
while (instance !== null) {
var _instance2 = instance,
stateNode = _instance2.stateNode,
tag = _instance2.tag; // Handle listeners that are on HostComponents (i.e. )
if (tag === HostComponent && stateNode !== null) {
lastHostComponent = stateNode; // createEventHandle listeners
if (reactEventName !== null) {
var listener = getListener(instance, reactEventName);
console.log('qy:-----listener',listener);
if (listener != null) {
listeners.push(createDispatchListener(instance, listener, lastHostComponent));
}
}
} // If we are only accumulating events for the target, then we don't
// continue to propagate through the React fiber tree to find other
// listeners.
if (accumulateTargetOnly) {
break;
}
instance = instance.return;
}
return listeners;
}
// 自顶向下查找
function getListener(inst, registrationName) {
var stateNode = inst.stateNode;
if (stateNode === null) {
// Work in progress (ex: onload events in incremental mode).
return null;
}
var props = getFiberCurrentPropsFromNode(stateNode);
if (props === null) {
// Work in progress.
return null;
}
var listener = props[registrationName];
if (shouldPreventMouseEvent(registrationName, inst.type, props)) {
return null;
}
if (!(!listener || typeof listener === 'function')) {
{
throw Error( "Expected `" + registrationName + "` listener to be a function, instead got a value of `" + typeof listener + "` type." );
}
}
return listener;
}
执行顺序processDispatchQueue
->processDispatchQueueItemsInOrder
->executeDispatch
processDispatchQueue
遍历dispatchQueue
,processDispatchQueueItemsInOrder
遍历某类事件的所有listeners
,executeDispatch
,执行函数
for (var i = 0; i < dispatchQueue.length; i++) {
var _dispatchQueue$i = dispatchQueue[i],
event = _dispatchQueue$i.event,
listeners = _dispatchQueue$i.listeners;
processDispatchQueueItemsInOrder(event, listeners, inCapturePhase); // event system doesn't use pooling.
} // This would be a good time to rethrow if any of the event handlers threw.
for (var i = dispatchListeners.length - 1; i >= 0; i--) {
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;
}
react为什么给click事件的hander绑定上空函数?
从浏览器的事件监听可以看到react将这个handler处理成空函数,源码内,为什么要处理?从源码内部可以知道是为了解决移动 Safari
无法正确触发气泡点击事件
if (typeof rawProps.onClick === 'function') {
// TODO: This cast may not be sound for SVG, MathML or custom elements.
trapClickOnNonInteractiveElement(domElement);
}
function trapClickOnNonInteractiveElement(node) {
// Mobile Safari does not fire properly bubble click events on
node.onclick = noop;
}
react为什么要自己实现一个合成事件?
-
节省内存消耗。
浏览器在处理事件的挂载和销毁会消耗内存,而react通过root挂载监听,委托分发,节省了内存消耗。
-
跨端兼容,实现全浏览器得一致性,统一规范
react如何阻止冒泡事件?
stopPropagation: function () {
var event = this.nativeEvent;
if (!event) {
return;
}
if (event.stopPropagation) {
event.stopPropagation();
} else if (typeof event.cancelBubble !== 'unknown') {
event.cancelBubble = true;
}
this.isPropagationStopped = functionThatReturnsTrue;
},
借助原生event.stopPropagation
去阻止。并且在executeDispatch
中检测到isPropagationStopped
为true
时,不执行listener
,达到阻止冒泡