前言
本文章是基于16.13.1的react版本。故里面有部分代码逻辑和16.8.6以及之前的版本已经发生较大差异,看本文之前,欢迎大家先看下以下链接,了解react v17.0版本都做了那些升级和变更。
官方说明:React v17.0 Release Candidate: No New Features
与本文相关的:1. 更改事件委托;2. “去除事件池“概念(事件合成时)。
一、事件存储&事件注册
1. 流程图
2. 流程细节
事件注册或者说绑定一般在初始化DOM节点的时候,所以我们直接以这种场景入手来说。在初始化的时候我们会调用到finalizeInitialChildren
,再进入setInitialProperties
来初始化属性:
// react-dom/src/client/ReactDOMComponent.js
export function setInitialProperties(
domElement: Element,
tag: string,
rawProps: Object,
rootContainerElement: Element | Document,
): void {
switch (tag) {
// ...
case 'input':
// ...
break;
case 'option':
// ...
break;
case 'select':
// ...
break;
case 'textarea':
// ...
break;
default:
props = rawProps;
}
assertValidProps(tag, props);
// 调用setInitialDOMProperties方法来真正得初始化 DOM 属性
setInitialDOMProperties(
tag,
domElement,
rootContainerElement,
props,
isCustomComponentTag,
);
// ...
}
紧接着进入setInitialDOMProperties
方法中:
// react-dom/src/client/ReactDOMComponent.js
function setInitialDOMProperties(
tag: string,
domElement: Element,
rootContainerElement: Element | Document,
nextProps: Object,
isCustomComponentTag: boolean,
):void {
for (const propKey in nextProps) {
const nextProp = nextProps[propKey];
if (propKey === STYLE) {
// ...
} else if (registrationNameDependencies.hasOwnProperty(propKey)) {
if (nextProp != null) {
if (__DEV__ && typeof nextProp !== 'function') {
warnForInvalidEventListener(propKey, nextProp);
}
ensureListeningTo(rootContainerElement, propKey, domElement);
}
}
}
}
当propKey在registrationNameDependencies
列表中时,会调用ensureListeningTo方法。这里的registrationNameDependencies
存储了React事件类型与浏览器原生事件类型映射的一个map对象。
registrationNameDependencies合集
其中"onChange"的dependences(下面会用到)
接下来我们接续往下看下ensureListeningTo
的代码:
// react-dom/src/client/ReactDOMComponent.js
export function ensureListeningTo(
rootContainerInstance: Element | Node,
reactPropEvent: string, // 例如onChange
targetElement: Element | null,
): void {
const rootContainerElement =
rootContainerInstance.nodeType === COMMENT_NODE
? rootContainerInstance.parentNode // body
: rootContainerInstance; // div#root,即当前插入的dom根节点
listenToReactEvent(
reactPropEvent,
((rootContainerElement: any): Element),
targetElement,
);
}
rootContainerElement
是 React 应用的挂载点,接下来我们看下listenToReactEvent
方法:
// react-dom/src/events/DOMPluginEventSystem.js
export function listenToReactEvent(
reactEvent: string, // 例如onChange
rootContainerElement: Element,
targetElement: Element | null,
): void {
// dependences这边可以理解为事件依赖,就是说注册某个事件,react会强制依赖其他事件。
const dependencies = registrationNameDependencies[reactEvent];
const dependenciesLength = dependencies.length;
const isPolyfillEventPlugin = dependenciesLength !== 1;
if (isPolyfillEventPlugin) {
// 首次返回一个空的map对象
const listenerMap = getEventListenerMap(rootContainerElement);
// listenerMap不包含当前事件属性,就进入判断(has是判断属性是否存在,即使内容为null,也是返回true)
// 也就是同一种事件只会注册一遍,onChange、onClick等等
if (!listenerMap.has(reactEvent)) {
// 给对象添加一个reactEvent属性,值为null
listenerMap.set(reactEvent, null);
for (let i = 0; i < dependenciesLength; i++) {
// 循环遍历dependencies
listenToNativeEvent(
dependencies[i], // dependence
false,
rootContainerElement,
targetElement,
);
}
}
} else {
const isCapturePhaseListener =
reactEvent.substr(-7) === 'Capture' &&
reactEvent.substr(-14, 7) !== 'Pointer';
listenToNativeEvent(
dependencies[0],
isCapturePhaseListener,
rootContainerElement,
targetElement,
);
}
}
const internalEventHandlersKey = '__reactEvents/div> + randomKey;
export function getEventListenerMap(node: EventTarget): ElementListenerMap {
// 获取当前节点的internalEventHandlersKey这个属性值
let elementListenerMap = (node: any)[internalEventHandlersKey];
if (elementListenerMap === undefined) {
elementListenerMap = (node: any)[internalEventHandlersKey] = new Map();
}
return elementListenerMap;
}
【事件存储】
这里进入listenToNativeEvent方法:
// react-dom/src/events/DOMPluginEventSystem.js
export function listenToNativeEvent(
domEventName: DOMEventName, // 'change'、'click'、'focusin'等dependence
isCapturePhaseListener: boolean,
rootContainerElement: EventTarget,
targetElement: Element | null,
isPassiveListener?: boolean,
listenerPriority?: EventPriority,
eventSystemFlags?: EventSystemFlags = PLUGIN_EVENT_SYSTEM,
): void {
let target = rootContainerElement; // div#root
// 这边去获取上面提到的那个map对象
const listenerMap = getEventListenerMap(target);
// listenerMapKey返回的形式为“change_bubble | change_capture”;
const listenerMapKey = getListenerMapKey(
domEventName,
isCapturePhaseListener,
);
// 判断listenerMap中是否存在listenerMapKey
const listenerEntry = ((listenerMap.get(
listenerMapKey,
): any): ElementListenerMapEntry | void);
// 判断是否需要更新
const shouldUpgrade = shouldUpgradeListener(listenerEntry, isPassiveListener);
// 如果不存在当前事件,或者需要更新,进入判断
if (listenerEntry === undefined || shouldUpgrade) {
// ...
// addTrappedEventListener内部就是做了:在target上进行事件监听,并返回dispatchEvent函数
const listener = addTrappedEventListener(
target, // DOM container
domEventName, // dependence
eventSystemFlags,
isCapturePhaseListener, // false
false,
isPassiveListener, // undefined
listenerPriority,
);
// 最终这个listenerMap会变成{onChange: null, change_bubble: {passive: isPassiveListener, listener}, click_bubble: {passive: isPassiveListener, listener}, focusin_bubble: {passive: isPassiveListener, listener}}
listenerMap.set(listenerMapKey, {passive: isPassiveListener, listener});
}
}
export function getListenerMapKey(
domEventName: DOMEventName,
capture: boolean,
): string {
return `${domEventName}__${capture ? 'capture' : 'bubble'}`;
}
listenerMap的结构
【事件注册】
上面代码中,真正的事件监听操作的入口在addTrappedEventListener方法里:
// react-dom/src/events/DOMPluginEventSystem.js
function addTrappedEventListener(
targetContainer: EventTarget,
domEventName: DOMEventName, // dependence
eventSystemFlags: EventSystemFlags,
isCapturePhaseListener: boolean,
isDeferredListenerForLegacyFBSupport?: boolean,
isPassiveListener?: boolean,
listenerPriority?: EventPriority,
): any => void {
// 这段代码尤为重要,通过传入的domEventName获取当前事件的优先级,返回的是经过包装过的三类dispatchEvent事件
// 分别为dispatchDiscreteEvent =>0 | dispatchUserBlockingUpdate =>1 | dispatchEvent=>2
let listener = createEventListenerWrapperWithPriority(
targetContainer,
domEventName,
eventSystemFlags,
listenerPriority,
);
if (isCapturePhaseListener) {
if (enableCreateEventHandleAPI && isPassiveListener !== undefined) {
unsubscribeListener = addEventCaptureListenerWithPassiveFlag(
targetContainer,
domEventName,
listener,
isPassiveListener,
);
} else {
// 入参listener:dispatchEvent;unsubscribeListener=dispatchEvent;
unsubscribeListener = addEventCaptureListener(
targetContainer,
domEventName,
listener,
);
}
} else {
if (enableCreateEventHandleAPI && isPassiveListener !== undefined) {
unsubscribeListener = addEventBubbleListenerWithPassiveFlag(
targetContainer,
domEventName,
listener,
isPassiveListener,
);
} else {
unsubscribeListener = addEventBubbleListener(
targetContainer,
domEventName,
listener,
);
}
}
return unsubscribeListener;
}
// 函数:createEventListenerWrapperWithPriority
export function createEventListenerWrapperWithPriority(
targetContainer: EventTarget,
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
priority?: EventPriority,
): Function {
const eventPriority =
priority === undefined
? getEventPriorityForPluginSystem(domEventName)
: priority;
let listenerWrapper;
switch (eventPriority) {
case DiscreteEvent:
listenerWrapper = dispatchDiscreteEvent;
break;
case UserBlockingEvent:
a = dispatchUserBlockingUpdate;
break;
case ContinuousEvent:
default:
listenerWrapper = dispatchEvent;
break;
}
return listenerWrapper.bind(
null,
domEventName,
eventSystemFlags,
targetContainer, // document
);
}
上面的listener等于createEventListenerWrapperWithPriority
的方法调用,该方法内部主要去判断了当前dependence事件的优先级,再对应到不同的dispatch方法,主要是三类(这边先略讲,和本次主题关系不大,主要和fiber的分片机制相关)。
最后,addEventBubbleListener和addEventCaptureListener
这两个方法大家就很熟悉了:
export function addEventBubbleListener(
target: EventTarget, // div#root
eventType: string, // change
listener: Function, // dispatchEvent
): Function {
target.addEventListener(eventType, listener, false);
return listener;
}
export function addEventCaptureListener(
target: EventTarget,
eventType: string,
listener: Function,
): Function {
// listener为dispatchEvent,不是真正的回调!!!
target.addEventListener(eventType, listener, true);
return listener;
}
到这边就把事件通过事件代理的方式绑定到了target
上。当然对于特殊的节点的不会冒泡的事件,在setInitialProperties
中已经事先直接绑定到节点上了。
总结:事件注册的流程,就是遍历props中的event,然后将事件和其依赖事件都挂载到**target
上,当中所有的事件的回调函数走的都是dispatchEvent,并且相同类型的事件只会挂在一次。打个比方,如果我绑定一个onChange事件,那么react不仅仅只绑定一个onChange事件到****target
****上,还会绑定许多依赖事件上去,如focus,blur,input等等,组件中声明的事件并不会保存起来,而仅仅是将事件类型以及dispatchEvent函数绑定到target
**元素上,实现事件委派。
二、事件合成&事件分发&事件执行
1. 流程图
2. 流程细节
第一部分事件注册有讲事件绑定的时候,只是传了一个dispatchEvent进行事件委托,那当在一个dom节点上进行onChange时,怎么触发真正的回调呢?这边我们从dispatchEvent入手,来看一下进步原理:
// react-dom/rc/events/ReactDOMEventListener.js
export function dispatchEvent(
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
targetContainer: EventTarget,
nativeEvent: AnyNativeEvent,
): void {
// ...
dispatchEventForPluginEventSystem(
domEventName,
eventSystemFlags,
nativeEvent,
null,
targetContainer,
);
}
export function dispatchEventForPluginEventSystem(
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
nativeEvent: AnyNativeEvent,
targetInst: null | Fiber,
targetContainer: EventTarget,
): void {
// 批处理更新方法
batchedEventUpdates(() =>
dispatchEventsForPlugins(
domEventName,
eventSystemFlags,
nativeEvent,
ancestorInst,
targetContainer,
),
);
}
// 将全局变量isBatchingEventUpdates设置为true
export function batchedEventUpdates(fn, a, b) {
if (isBatchingEventUpdates) {
return fn(a, b);
}
isBatchingEventUpdates = true;
try {
// 这是个工具函数,返回fn(a, b);
return batchedEventUpdatesImpl(fn, a, b);
} finally {
isBatchingEventUpdates = false;
finishEventHandler();
}
}
function dispatchEventsForPlugins(
domEventName: DOMEventName,
eventSystemFlags: EventSystemFlags,
nativeEvent: AnyNativeEvent,
targetInst: null | Fiber,
targetContainer: EventTarget,
): void {
const nativeEventTarget = getEventTarget(nativeEvent);
const dispatchQueue: DispatchQueue = [];
// 进行事件合成
extractEvents(
dispatchQueue, // []
domEventName, // dependence
targetInst,
nativeEvent, // 原生事件
nativeEventTarget, // 当前dom元素
eventSystemFlags,
targetContainer, // div#root
);
// 按顺序执行事件队列,此时dispatchQueue已经变成[onChange, [{instance, listener, currentTarget}, ...]]
processDispatchQueue(dispatchQueue, eventSystemFlags);
}
dispatchEventsForPlugins方法是事件callback调用的核心。它主要做两件事情,一方面利用浏览器回传的原生事件构造出React合成事件,另一方面采用队列的方式处理events。先看如何构造合成事件:
【事件合成】
extractEvents
这个方法就是调用各种插件来创建相应函数的合成事件,一共有6种插件,这边用到了5个。事件的合成,冒泡的处理以及事件回调的查找都是在合成阶段完成的。
// react-dom/rc/events/ReactDOMEventListener.js
function extractEvents(
dispatchQueue: DispatchQueue, // 初始为[]
domEventName: DOMEventName, // dependence
targetInst: null | Fiber, //
nativeEvent: AnyNativeEvent, // 原生事件
nativeEventTarget: null | EventTarget, // 当前dom元素
eventSystemFlags: EventSystemFlags,
targetContainer: EventTarget,
) {
SimpleEventPlugin.extractEvents(
dispatchQueue,
domEventName,
targetInst,
nativeEvent,
nativeEventTarget,
eventSystemFlags,
targetContainer,
);
const shouldProcessPolyfillPlugins = (eventSystemFlags & SHOULD_NOT_PROCESS_POLYFILL_EVENT_PLUGINS) === 0;
if (shouldProcessPolyfillPlugins) {
EnterLeaveEventPlugin.extractEvents(
...
);
ChangeEventPlugin.extractEvents(
...
);
SelectEventPlugin.extractEvents(
...
);
BeforeInputEventPlugin.extractEvents(
...
);
}
}
我们以ChangeEventPlugin插件举例:
// react-dom/src/client/events/ChangeEventPlugin.js
function extractEvents(
dispatchQueue: DispatchQueue,
domEventName: DOMEventName,
targetInst: null | Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: null | EventTarget,
eventSystemFlags: EventSystemFlags,
targetContainer: null | EventTarget,
) {
const targetNode = targetInst ? getNodeFromInstance(targetInst) : window;
let getTargetInstFunc, handleEventFunc;
// 这边判断当前的节点符不符合当前插件创建相应合成事件的要求
if (shouldUseChangeEvent(targetNode)) {
getTargetInstFunc = getTargetInstForChangeEvent;
} else if (isTextInputElement(((targetNode: any): HTMLElement))) {
if (isInputEventSupported) {
getTargetInstFunc = getTargetInstForInputOrChangeEvent;
} else {
getTargetInstFunc = getTargetInstForInputEventPolyfill;
handleEventFunc = handleEventsForInputEventPolyfill;
}
} else if (shouldUseClickEvent(targetNode)) {
getTargetInstFunc = getTargetInstForClickEvent;
}
if (getTargetInstFunc) {
const inst = getTargetInstFunc(domEventName, targetInst);
if (inst) {
// dispatchQueue一开始为[]
createAndAccumulateChangeEvent(
dispatchQueue,
inst,
nativeEvent,
nativeEventTarget,
);
return;
}
}
if (handleEventFunc) {
handleEventFunc(domEventName, targetNode, targetInst);
}
紧接着我们来看下createAndAccumulateChangeEvent
方法:
// react-dom/src/client/events/plugins/ChangeEventPlugin.js
function createAndAccumulateChangeEvent(
dispatchQueue,
inst,
nativeEvent,
target,
) {
// 创建合成事件
const event = new SyntheticEvent('onChange', null, nativeEvent, target);
event.type = 'change';
enqueueStateRestore(((target: any): Node));
// 事件分发,给dispatchQueue赋值
accumulateTwoPhaseListeners(inst, dispatchQueue, event);
}
【事件分发】
// react-dom/src/client/events/DOMPluginEventSystem.js
export function accumulateTwoPhaseListeners(
targetFiber: Fiber | null,
dispatchQueue: DispatchQueue, // []
event: ReactSyntheticEvent, // onChange合成事件
): void {
const bubbled = event._reactName; // 就是“onChange”
const captured = bubbled !== null ? bubbled + 'Capture' : null; // 就是“onChangeCapture”
const listeners: Array<DispatchListener> = [];
let instance = targetFiber;
// 这边向上查找到所有当前类型事件的回调函数,重要!!!
while (instance !== null) {
const {stateNode, tag} = instance;
if (tag === HostComponent && stateNode !== null) {
const currentTarget = stateNode;
if (captured !== null) {
// 返回当前节点的回调函数
const captureListener = getListener(instance, captured);
if (captureListener != null) {
// 捕获,插入数组头部
listeners.unshift(
// 工具函数,返回对象{instance, listener, currentTarget}
createDispatchListener(instance, captureListener, currentTarget),
);
}
}
if (bubbled !== null) {
// 返回当前节点的回调函数
const bubbleListener = getListener(instance, bubbled);
if (bubbleListener != null) {
// 冒泡,插入数组尾部
listeners.push(
// 工具函数,返回对象{instance, listener, currentTarget}
createDispatchListener(instance, bubbleListener, currentTarget),
);
}
}
}
instance = instance.return;
}
// listeners即某一类合成事件的所有回调函数的集合,[{instance, listener, currentTarget}, ...]
if (listeners.length !== 0) {
// createDispatchEntry返回的是对象{event, listeners};
// dispatchQueue最后为[{event, listeners}, ...], 即[{onChange, [{instance, listener, currentTarget}, ...]}, ...]
dispatchQueue.push(createDispatchEntry(event, listeners));
}
}
export default function getListener(
inst: Fiber, // 当前实例
registrationName: string, // “onChange”
): Function | null {
...
// 返回dom上的props
const props = getFiberCurrentPropsFromNode(stateNode);
if (props === null) {
// Work in progress.
return null;
}
// 获取到当前事件的回调函数
const listener = props[registrationName]
return listener;
}
以上就进行了事件合成以及拿到了所有回调函数组成的队列。这边的事件合成和16.8版本即以前有很大的不同,本版本已经去掉了“事件池“的概念,每次的合成事件都是重新new的!!!接下来就到事件执行环节了:
【事件执行】
// react-dom/rc/events/DOMPluginEventSystem.js
export 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);
}
rethrowCaughtError();
}
// react-dom/rc/events/DOMPluginEventSystem.js
function processDispatchQueueItemsInOrder(
event: ReactSyntheticEvent, // "onChange"合成事件
dispatchListeners: Array<DispatchListener>,
inCapturePhase: boolean,
): void {
let previousInstance;
if (inCapturePhase) {
// 遍历dispatchListeners
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++) {
// 取出每个回调函数所在的对象里的instance, currentTarget, listener
const {instance, currentTarget, listener} = dispatchListeners[i];
if (instance !== previousInstance && event.isPropagationStopped()) {
return;
}
// 最终执行回调
executeDispatch(event, listener, currentTarget);
previousInstance = instance;
}
}
}
function executeDispatch(
event: ReactSyntheticEvent, // onChange
listener: Function, // 对应的回调函数
currentTarget: EventTarget,
): void {
// "onChange"
const type = event.type || 'unknown-event';
// 将当前dom元素赋值给合成事件的currentTarget
event.currentTarget = currentTarget;
// 执行回调函数,listener为回调函数, event为合成事件,最后执行listener(event)这个方法调用
// 这样就回调到了我们在JSX中注册的callback。比如onClick={(event) => {console.log(1)}}
// 现在就明白了callback怎么被调用的,以及event参数怎么传入callback里面的了
invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event);
event.currentTarget = null;
}
// shared/invokeGuardedCallbackImpl.js
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 {
//最后就是在这里执行的回调函数
func.apply(context, funcArgs);
} catch (error) {
this.onError(error);
}
}
这样就完成了事件执行了。