为什么使用合成事件 ?
- 合成事件 它符合W3C规范,
在底层抹平了不同浏览器的差异,在上层面向开发者暴露统一的、稳定的、与 DOM 原生事件相同的事件接口. - 大部分处理逻辑都在底层处理了,这对后期的 ssr 和跨端支持度很高.
原理
🔥 DEMO
import { createPortal } from 'react-dom'
function Child(){
return <button onClick={()=>console.log('child')}>Child</button>
}
function App() {
return <div onClick={()=>console.log('parent')}>
<Child />
{createPortal( <button onClick={()=>console.log('portal')}>Portal</button>,document.body)}
</div>
}
事件绑定
- 🔥 事件绑定都依赖于
addTrappedEventListener完成
function addTrappedEventListener(targetContainer, domEventName,isCapturePhaseListener){
// 创建 react 事件处理函数
var listener = createEventListenerWrapperWithPriority(targetContainer, domEventName);
//
if(isCapturePhaseListener){
addEventCaptureListener(targetContainer, domEventName, listener)
} else {
addEventBubbleListener(targetContainer, domEventName, listener)
}
}
function addEventBubbleListener(target, eventType, listener) {
target.addEventListener(eventType, listener, false);
return listener;
}
function addEventCaptureListener(target, eventType, listener) {
target.addEventListener(eventType, listener, true);
return listener;
}
- 事件分为 可代理事件 和 不可代理事件(
NonDelegated) - 不可代理事件在
completeWork中处理, 目前仅有scroll事件.
- registrationNameDependencies: 维护着react事件属性和原生DOM事件名称的映射关系
function setInitialDOMProperties(){
// registrationNameDependencies
// 维护着react事件属性和原生DOM事件名称的映射关系
// { onScroll: ['scroll'] }
if (registrationNameDependencies.hasOwnProperty(propKey)) {
if (nextProp != null) {
if (propKey === 'onScroll') {
// 直接绑定到DOM上
listenToNonDelegatedEvent('scroll', domElement);
}
}
}
}
- 可代理事件在
createRoot/listenToAllSupportedEvents中绑定.
function createRoot(container, options) {
//
var rootContainerElement = container.nodeType === COMMENT_NODE ? container.parentNode : container;
// 绑定可代理的事件
listenToAllSupportedEvents(rootContainerElement);
}
function listenToAllSupportedEvents(){
// allNativeEvents 记录着所有的dom事件名称
allNativeEvents.forEach(function (domEventName) {
// We handle selectionchange separately because it
// doesn't bubble and needs to be on the document.
// 🔥 可以看到 selectionchange 绑定在document上,而不是 root上
if (domEventName !== 'selectionchange') {
if (!nonDelegatedEvents.has(domEventName)) {
// 冒泡阶段监听
listenToNativeEvent(domEventName, false, rootContainerElement);
}
// 捕获阶段监听
listenToNativeEvent(domEventName, true, rootContainerElement);
}
});
}
function listenToNativeEvent(){
addTrappedEventListener(target, domEventName, eventSystemFlags, isCapturePhaseListener);
}
-
react中的事件处理函数
-
🔥 不同的事件存在不同的事件优先级, 不同的优先级也对应着不同的事件处理函数.
- 比如
click对应着dispatchDiscreteEvent
function createEventListenerWrapperWithPriority(targetContainer, domEventName, eventSystemFlags) {
// 根据事件名称获取事件优先级
var eventPriority = getEventPriority(domEventName);
var listenerWrapper;
switch (eventPriority) {
case DiscreteEventPriority:
// click对应着 dispatchDiscreteEvent
listenerWrapper = dispatchDiscreteEvent;
break;
case ContinuousEventPriority:
listenerWrapper = dispatchContinuousEvent;
break;
case DefaultEventPriority:
default:
listenerWrapper = dispatchEvent;
break;
}
return listenerWrapper.bind(null, domEventName, eventSystemFlags, targetContainer);
}
dispatchDiscreteEvent/dispatchContinuousEvent都是基于dispatchEvent的,只是对应着不同的更新优先级
function dispatchDiscreteEvent(domEventName, eventSystemFlags, container, nativeEvent) {
var previousPriority = getCurrentUpdatePriority();
var prevTransition = ReactCurrentBatchConfig.transition;
ReactCurrentBatchConfig.transition = null;
try {
setCurrentUpdatePriority(DiscreteEventPriority);
dispatchEvent(domEventName, eventSystemFlags, container, nativeEvent);
} finally {
setCurrentUpdatePriority(previousPriority);
ReactCurrentBatchConfig.transition = prevTransition;
}
}
function dispatchContinuousEvent(domEventName, eventSystemFlags, container, nativeEvent) {
var previousPriority = getCurrentUpdatePriority();
var prevTransition = ReactCurrentBatchConfig.transition;
ReactCurrentBatchConfig.transition = null;
try {
setCurrentUpdatePriority(ContinuousEventPriority);
dispatchEvent(domEventName, eventSystemFlags, container, nativeEvent);
} finally {
setCurrentUpdatePriority(previousPriority);
ReactCurrentBatchConfig.transition = prevTransition;
}
}
function dispatchEvent(domEventName, eventSystemFlags, targetContainer, nativeEvent) {
if (!_enabled) {
return;
}
{
dispatchEventWithEnableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay(domEventName, eventSystemFlags, targetContainer, nativeEvent);
}
}
事件触发
- 🔥 通过上面的分析 已经清楚了真正绑定的DOM事件处理函数为
dispatchEvent return_targetInst为 DOM 对应的fiber对象
function dispatchEventWithEnableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay(){
// 🔥
var blockedOn = findInstanceBlockingEvent(domEventName, eventSystemFlags, targetContainer, nativeEvent);
if (blockedOn === null) {
//
dispatchEventForPluginEventSystem(domEventName, eventSystemFlags, nativeEvent, return_targetInst, targetContainer);
clearIfContinuousEvent(domEventName, nativeEvent);
return;
}
}
// 🔥 事件对象 对应的 fiber对象
var return_targetInst = null
function findInstanceBlockingEvent(domEventName, eventSystemFlags, targetContainer, nativeEvent) {
// TODO: Warn if _enabled is false.
return_targetInst = null;
var nativeEventTarget = getEventTarget(nativeEvent);
var targetInst = getClosestInstanceFromNode(nativeEventTarget);
return_targetInst = targetInst; // We're not blocked on anything.
return null;
}
- 🔥
dispatchEventForPluginEventSystem
function dispatchEventsForPlugins(domEventName, eventSystemFlags, nativeEvent, targetInst, targetContainer) {
var nativeEventTarget = getEventTarget(nativeEvent);
//
var dispatchQueue = [];
// 1. 收集需要执行的事件处理函数
extractEvents$5(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags);
// 2. 执行需要执行的事件处理函数
processDispatchQueue(dispatchQueue, eventSystemFlags);
}
- 收集事件处理函数, 通过
fiber.return向上收集所有祖先 hostComponent's fiber节点(原生DOM)的事件处理函数.
function extractEvents$5(dispatchQueue, domEventName){
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, nativeEvent) {
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. <div>)
// 收集 HostComponent
if (tag === HostComponent && stateNode !== null) {
lastHostComponent = stateNode; // createEventHandle listeners
if (reactEventName !== null) {
var listener = getListener(instance, reactEventName);
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;
} // If we are processing the onBeforeBlur event, then we need to take
// 🔥
instance = instance.return;
}
return listeners;
}
- 执行事件处理函数, 模拟事件冒泡和捕获的事件执行顺序 执行收集到的事件处理函数.
function processDispatchQueueItemsInOrder(event, dispatchListeners, inCapturePhase) {
var previousInstance;
if (inCapturePhase) {
// 捕获: 从内到外
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;
}
} else {
// 冒泡: 从外到内
for (var _i = 0; _i < dispatchListeners.length; _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;
}
}
}