目录
前言
本文是React源码学习系列第八篇,该系列整体都基于React18.0.0版本源码。旨在学习过程中记录一些个人的理解。该篇介绍React 事件系统。
概念
更新流程通常由“产生交互”开始,“交互”则与各种事件相关,“事件”由React事件系统产生。事件系统存在的意义在于--React用Fiber Tree数据结构描述UI,事件系统则基于Fiber Tree描述UI交互。
SyntheticEvent(合成事件)
是对浏览器原生事件对象的一层封装,拥有与原生事件相同的API,抹平了不同浏览器在“事件对象”间的差异,但是对于不支持某一事件的浏览器,不提供polyfill。
模拟实现事件传播机制。
利用事件委托的原理,React基于Fiber Tree实现了事件的“捕获、目标、冒泡”流程,并在这套事件传播机制中加入了许多“行特性”,比如:
- 不同事件对应不同优先级。
- 定制事件名。
- 定制事件行为。
React事件与原生事件对应关系
let registrationNameDependencies = {
"onClick": ["click"],
"onClickCapture": ["click"],
"onInput": ["input"],
"onInputCapture": ["input"],
// onChange事件由以下原生事件合成
"onChange": ["change", "click", "focusin", "focusout", "input", "keydown", "keyup", "selectionchange"],
"onChangeCapture": ["change", "click", "focusin", "focusout", "input", "keydown", "keyup", "selectionchange"],
"onSelect": ["focusout", "contextmenu", "dragend", "focusin", "keydown", "keyup", "mousedown", "mouseup", "selectionchange"],
// 忽略其他...
}
合成事件对象工厂函数
根据传入不同的原生事件对象特有属性,返回SyntheticBaseEvent构造函数。
function createSyntheticEvent(Interface: EventInterfaceType) {
function SyntheticBaseEvent(
reactName: string | null,
reactEventType: string,
targetInst: Fiber,
nativeEvent: {[propName: string]: mixed},
nativeEventTarget: null | EventTarget,
) {
this._reactName = reactName;
this._targetInst = targetInst;
this.type = reactEventType;
this.nativeEvent = nativeEvent;
this.target = nativeEventTarget;
this.currentTarget = null;
// 把传进来的属性,合并到实例上。
for (const propName in Interface) {
if (!Interface.hasOwnProperty(propName)) {
continue;
}
const normalize = Interface[propName];
// 除了函数,其他默认都是给0,所以会执行函数
if (normalize) {
this[propName] = normalize(nativeEvent);
} else {
this[propName] = nativeEvent[propName];
}
}
const defaultPrevented =
nativeEvent.defaultPrevented != null
? nativeEvent.defaultPrevented
: nativeEvent.returnValue === false;
if (defaultPrevented) {
this.isDefaultPrevented = functionThatReturnsTrue;
} else {
this.isDefaultPrevented = functionThatReturnsFalse;
}
this.isPropagationStopped = functionThatReturnsFalse;
return this;
}
// 扩展原型方法
Object.assign(SyntheticBaseEvent.prototype, {
// 模拟阻止默认事件方法。
preventDefault: function() {
this.defaultPrevented = true;
const event = this.nativeEvent;
if (!event) {
return;
}
// 执行原生阻止默认事件方法。
if (event.preventDefault) {
event.preventDefault();
} else if (typeof event.returnValue !== 'unknown') {
event.returnValue = false;
}
// 设为true
this.isDefaultPrevented = functionThatReturnsTrue;
},
// 模拟阻止冒泡方法。
stopPropagation: function() {
const event = this.nativeEvent;
if (!event) {
return;
}
// 执行原生阻止冒泡方法。
if (event.stopPropagation) {
event.stopPropagation();
} else if (typeof event.cancelBubble !== 'unknown') {
event.cancelBubble = true;
}
// 设为true
this.isPropagationStopped = functionThatReturnsTrue;
},
});
return SyntheticBaseEvent;
}
代码梳理
function createRoot(
container: Element | DocumentFragment,
options?: CreateRootOptions,
): RootType {
// container为document.getElementById('root');
const rootContainerElement: Document | Element | DocumentFragment =
container.nodeType === COMMENT_NODE
? (container.parentNode: any)
: container; //判断传入的container是不是注释标签,是就取它父元素
// 事件绑定
listenToAllSupportedEvents(rootContainerElement);
}
listenToAllSupportedEvents
判断是否是捕获阶段事件,没有冒泡阶段的。
function listenToAllSupportedEvents(rootContainerElement: EventTarget) {
if (!(rootContainerElement: any)[listeningMarker]) {
(rootContainerElement: any)[listeningMarker] = true;
allNativeEvents.forEach(domEventName => {
// 捕获事件
if (!nonDelegatedEvents.has(domEventName)) {
listenToNativeEvent(domEventName, false, rootContainerElement);
}
// 冒泡事件
listenToNativeEvent(domEventName, true, rootContainerElement);
});
}
}
listenToNativeEvent
没有冒泡阶段的方法,打上捕获标记。
function listenToNativeEvent(
domEventName: DOMEventName,
isCapturePhaseListener: boolean,
target: EventTarget,
): void {
let eventSystemFlags = 0;
// 是否捕获阶段
if (isCapturePhaseListener) {
eventSystemFlags |= IS_CAPTURE_PHASE;
}
addTrappedEventListener(
target,
domEventName,
eventSystemFlags,
isCapturePhaseListener,
);
}
addTrappedEventListener
- 给事件包装优先级
- 根据是否捕获阶段,注册对应阶段的事件
function addTrappedEventListener(
targetContainer: EventTarget,
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
isCapturePhaseListener: boolean,
isDeferredListenerForLegacyFBSupport?: boolean,
) {
// 事件包装优先级
let listener = createEventListenerWrapperWithPriority(
targetContainer,
domEventName,
eventSystemFlags,
);
let unsubscribeListener;
if (isCapturePhaseListener) {
//注册捕获事件
unsubscribeListener = addEventCaptureListener(
targetContainer,
domEventName,
listener,
);
} else {
//注册冒泡事件
unsubscribeListener = addEventBubbleListener(
targetContainer,
domEventName,
listener,
);
}
}
包装事件优先级
各种优先级都是手动设置对应优先级,然后调用dispatchEvent方法。
function createEventListenerWrapperWithPriority(
targetContainer: EventTarget,
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
): Function {
// 根据事件类型获取事件优先级
const eventPriority = getEventPriority(domEventName);
let listenerWrapper;
// 根据事件优先级注册不同的dispatch函数
switch (eventPriority) {
case DiscreteEventPriority: //最高优先级sync,第一车道
listenerWrapper = dispatchDiscreteEvent;
break;
case ContinuousEventPriority: //第三车道优先级
listenerWrapper = dispatchContinuousEvent;
break;
case DefaultEventPriority:// 最低优先级第五车道
default:
listenerWrapper = dispatchEvent;
break;
}
return listenerWrapper.bind(
null,
domEventName,
eventSystemFlags,
targetContainer,
);
}
dispatchEvent
找到触发事件元素的对应fiberNode。
// 保留核心代码
function dispatchEvent(
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
targetContainer: EventTarget,
nativeEvent: AnyNativeEvent, // 原生事件对象
) {
// 找到触发事件的元素对应的fiberNode,赋值给return_targetInst
findInstanceBlockingEvent(
domEventName,
eventSystemFlags,
targetContainer,
nativeEvent,
);
// 触发的主要函数
dispatchEventForPluginEventSystem(
domEventName,
eventSystemFlags,
nativeEvent,
return_targetInst,
targetContainer,
);
return;
}
dispatchEventForPluginEventSystem
在批处理环境下执行事件。
function dispatchEventForPluginEventSystem(
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
nativeEvent: AnyNativeEvent,
targetInst: null | Fiber,
targetContainer: EventTarget,
): void {
let ancestorInst = targetInst;
// 批处理
batchedUpdates(() =>
dispatchEventsForPlugins(
domEventName,
eventSystemFlags,
nativeEvent,
ancestorInst,
targetContainer,
),
);
}
dispatchEventsForPlugins
- 收集路径上的同类事件
- 模拟原生事件流程,根据调用事件执行阶段(捕获或者冒泡)执行收集到的时间。
function dispatchEventsForPlugins(
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
nativeEvent: AnyNativeEvent,
targetInst: null | Fiber,
targetContainer: EventTarget,
): void {
// event.target
const nativeEventTarget = getEventTarget(nativeEvent);
const dispatchQueue: DispatchQueue = [];
// 收集路径上的同类事件,并创建合成事件对象。
extractEvents(
dispatchQueue,
domEventName,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
targetContainer,
);
// 模拟原生事件执行。
processDispatchQueue(dispatchQueue, eventSystemFlags);
}
extractEvents
根据触发的事件名称,收集Fiber Tree上的同类事件。并根据不同的事件类型,创建对应的合成事件对象。
function extractEvents(
dispatchQueue: DispatchQueue,
domEventName: DOMEventName,
targetInst: null | Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: null | EventTarget,
eventSystemFlags: EventSystemFlags,
targetContainer: EventTarget,
): void {
const reactName = topLevelEventsToReactNames.get(domEventName); // 拿到原生事件对应的react事件
if (reactName === undefined) {
return;
}
let SyntheticEventCtor = SyntheticEvent;
let reactEventType: string = domEventName;
switch (domEventName) {
case 'keydown':
case 'keyup':
SyntheticEventCtor = SyntheticKeyboardEvent;
break;
default:
break;
}
const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
// 收集事件
const listeners = accumulateSinglePhaseListeners(
targetInst,
reactName,
nativeEvent.type,
inCapturePhase,
accumulateTargetOnly,
nativeEvent,
);
// listeners触发节点到HostRootFiber路径上的所有该类型事件列表。
if (listeners.length > 0) {
// 合成事件对象
const event = new SyntheticEventCtor(
reactName,
reactEventType,
null,
nativeEvent,
nativeEventTarget,
);
dispatchQueue.push({event, listeners});
}
}
accumulateSinglePhaseListeners
从触发事件的元素对应fiberNode开始,向上一直找到HostRootFiber结束,收集路径上的同类型事件。维护在一个Array里面。
function accumulateSinglePhaseListeners(
targetFiber: Fiber | null,
reactName: string | null,
nativeEventType: string,
inCapturePhase: boolean,
accumulateTargetOnly: boolean,
nativeEvent: AnyNativeEvent,
): Array<DispatchListener> {
const captureName = reactName !== null ? reactName + 'Capture' : null;
const reactEventName = inCapturePhase ? captureName : reactName;
let listeners: Array<DispatchListener> = [];
let instance = targetFiber;
let lastHostComponent = null;
// 从事件触发点到HostRootFiber收集事件
while (instance !== null) {
const {stateNode, tag} = instance;
// 处理HostComponents上的监听器
if (tag === HostComponent && stateNode !== null) {
lastHostComponent = stateNode;
if (reactEventName !== null) {
const listener = getListener(instance, reactEventName);
if (listener != null) {
// 返回 {instance,listener,lastHostComponent}
listeners.push(
createDispatchListener(instance, listener, lastHostComponent),
);
}
}
}
instance = instance.return;
}
return listeners;
}
getListener
从stateNode上拿到对应事件的回调函数。
function getListener(
inst: Fiber,
registrationName: string,
): Function | null {
const stateNode = inst.stateNode;
const props = getFiberCurrentPropsFromNode(stateNode);
const listener = props[registrationName];
return listener;
}
processDispatchQueue
为收集到的事件列表模拟原生事件流程,捕获、目标、冒泡。
function processDispatchQueue(
dispatchQueue: DispatchQueue,
eventSystemFlags: EventSystemFlags,
): void {
const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;
for (let i = 0; i < dispatchQueue.length; i++) {
const {event, listeners} = dispatchQueue[i];
processDispatchQueueItemsInOrder(event, listeners, inCapturePhase);
}
}
processDispatchQueueItemsInOrder
模拟实现捕获阶段事件及冒泡阶段事件。
function processDispatchQueueItemsInOrder(
event: ReactSyntheticEvent,
dispatchListeners: Array<DispatchListener>,
inCapturePhase: boolean,
): void {
let previousInstance;
// 执行捕获阶段事件
if (inCapturePhase) {
for (let i = dispatchListeners.length - 1; i >= 0; i--) {
const {instance, currentTarget, listener} = dispatchListeners[i];
if (instance !== previousInstance && event.isPropagationStopped()) {
return;
}
executeDispatch(event, listener, currentTarget);
previousInstance = instance;
}
} else {
// 执行冒泡阶段事件
for (let i = 0; i < dispatchListeners.length; i++) {
const {instance, currentTarget, listener} = dispatchListeners[i];
// 阻止冒泡
if (instance !== previousInstance && event.isPropagationStopped()) {
return;
}
executeDispatch(event, listener, currentTarget);
previousInstance = instance;
}
}
}
事件执行
function executeDispatch(
event: ReactSyntheticEvent, // 合成事件对象
listener: Function,
currentTarget: EventTarget,
): void {
const type = event.type || 'unknown-event';
event.currentTarget = currentTarget;
invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event);
event.currentTarget = null;
}
最终调用事件。
最终生产环境是调用invokeGuardedCallbackProd方法,执行实际的事件。
function invokeGuardedCallbackProd<A, B, C, D, E, F, Context>(
name: string | null,
func: (a: A, b: B, c: C, d: D, e: E, f: F) => mixed,
context: Context,
a: A,
b: B,
c: C,
d: D,
e: E,
f: F,
) {
// 拿到调用事件处理函数的实际传参
const funcArgs = Array.prototype.slice.call(arguments, 3);
try {
// 执行的时候绑定context 为undefined
func.apply(context, funcArgs);
} catch (error) {
this.onError(error);
}
}
批处理
个人理解18的架构下,这个应该没有用了,按照lane模型来批量处理的,每一次调度都会选出一个最高优先级,同样优先级的更新任务会在同一个更新流程里面执行。
function batchedUpdates<A, R>(fn: A => R, a: A): R {
// 缓存之前执行上下文
const prevExecutionContext = executionContext;
// 执行上下文加上批处理上下文
executionContext |= BatchedContext;
try {
return fn(a);
} finally {
// 恢复之前执行上下文
executionContext = prevExecutionContext;
}
}
参考
React设计原理 - 卡颂