事件绑定
事件绑定主要是在DOM初始化和更新阶段.
- 在初始化的时候会调用setInitialProperties方法
- 接着执行setInitialDOMProperties
- setInitialDOMProperties对props的key进行判断,不同的props有不同的处理方式,如style、class、autoFocus,innerHTML等,如果
registrationNameModules.hasOwnProperty(propKey)
为真,就表明是个注册过得事件,调用ensureListeningTo
开始绑定
ensureListeningTo
if (registrationNameModules.hasOwnProperty(propKey)) {
if (nextProp != null) {
if (true && typeof nextProp !== 'function') {
warnForInvalidEventListener(propKey, nextProp);
}
ensureListeningTo(rootContainerElement, propKey);
}
function ensureListeningTo(rootContainerElement, registrationName) {
var isDocumentOrFragment = rootContainerElement.nodeType === DOCUMENT_NODE || rootContainerElement.nodeType === DOCUMENT_FRAGMENT_NODE;
var doc = isDocumentOrFragment ? rootContainerElement : rootContainerElement.ownerDocument;
listenTo(registrationName, doc);
}
rootContainerElement
是 React 应用的挂载点,这个方法首先判断rootContainerElement是不是一个 document或者 Fragment,二者都不是的话,就设置doc为rootContainerElement.ownerDocument, 也就是当前节点的顶层的 document 对象。
可以看到事件委托就是在这里实现的,所有的事件最终都会被委托到 document 或者 fragment上去,大部分情况下都是 document。
listenTo
export function listenTo(
registrationName: string,
mountAt: Document | Element | Node,
): void {
//获取监听的节点
const listeningSet = getListeningSetForElement(mountAt);
//获取事件的依赖
const dependencies = registrationNameDependencies[registrationName];
for (let i = 0; i < dependencies.length; i++) {
const dependency = dependencies[i];
if (!listeningSet.has(dependency)) {
switch (dependency) {
case TOP_SCROLL:
trapCapturedEvent(TOP_SCROLL, mountAt);
break;
case TOP_FOCUS:
case TOP_BLUR:
trapCapturedEvent(TOP_FOCUS, mountAt);
trapCapturedEvent(TOP_BLUR, mountAt);
// We set the flag for a single dependency later in this function,
// but this ensures we mark both as attached rather than just one.
listeningSet.add(TOP_BLUR);
listeningSet.add(TOP_FOCUS);
break;
case TOP_CANCEL:
case TOP_CLOSE:
if (isEventSupported(getRawEventName(dependency))) {
trapCapturedEvent(dependency, mountAt);
}
break;
case TOP_INVALID:
case TOP_SUBMIT:
case TOP_RESET:
// We listen to them on the target DOM elements.
// Some of them bubble so we don't want them to fire twice.
break;
default:
const isMediaEvent = mediaEventTypes.indexOf(dependency) !== -1;
if (!isMediaEvent) {
trapBubbledEvent(dependency, mountAt);
}
break;
}
listeningSet.add(dependency);
}
}
}
可以看到除了一些特定的事件调用trapCapturedEvent外,多数事件调用trapBubbledEvent,分别对应捕获和冒泡阶段。
trapCapturedEvent和trapBubbledEvent
都调用了trapEventForPluginEventSystem
只是最后一个参数不同,一个为true,一个为false 表示是否捕获。
由于事件委托到了document上,所有的合成事件回调函数都是在冒泡阶段触发的,所以无论是React的冒泡还是捕获事件,都是晚于原生冒泡和捕获事件的响应的,如果原生事件调用了e.stopPropagation(),那么React事件将无法响应
export function trapBubbledEvent(
topLevelType: DOMTopLevelEventType,
element: Document | Element | Node,
): void {
trapEventForPluginEventSystem(element, topLevelType, false);
}
export function trapCapturedEvent(
topLevelType: DOMTopLevelEventType,
element: Document | Element | Node,
): void {
trapEventForPluginEventSystem(element, topLevelType, true);
}
trapEventForPluginEventSystem
首先判断事件的优先级,不同优先级的有不同的处理方法。
export const DiscreteEvent: EventPriority = 0;
export const UserBlockingEvent: EventPriority = 1;
export const ContinuousEvent: EventPriority = 2;
可以看到listener并不是我们自己写的事件回调,这里的listener只是记住了是什么类型的事件。
addEventBubbleListener和addEventCaptureListener
function trapEventForPluginEventSystem(
element: Document | Element | Node,
topLevelType: DOMTopLevelEventType,
capture: boolean,
): void {
let listener;
switch (getEventPriority(topLevelType)) {
case DiscreteEvent:
listener = dispatchDiscreteEvent.bind(
null,
topLevelType,
PLUGIN_EVENT_SYSTEM,
);
break;
case UserBlockingEvent:
listener = dispatchUserBlockingUpdate.bind(
null,
topLevelType,
PLUGIN_EVENT_SYSTEM,
);
break;
case ContinuousEvent:
default:
listener = dispatchEvent.bind(null, topLevelType, PLUGIN_EVENT_SYSTEM);
break;
}
const rawEventName = getRawEventName(topLevelType);
if (capture) {
addEventCaptureListener(element, rawEventName, listener);
} else {
addEventBubbleListener(element, rawEventName, listener);
}
}
function addEventBubbleListener(element, eventType, listener) {
element.addEventListener(eventType, listener, false);
}
function addEventCaptureListener(element, eventType, listener) {
element.addEventListener(eventType, listener, true);
}
到这里就通过事件代理的方式在container对象上绑定到了事件。
以click
事件为例,会调用dispatchDiscreteEvent
,最终还是dispatchEvent
方法被调用。该方法将在事件分发小节讲述。
function dispatchDiscreteEvent(topLevelType, eventSystemFlags, nativeEvent) {
flushDiscreteUpdatesIfNeeded(nativeEvent.timeStamp);
discreteUpdates(dispatchEvent, topLevelType, eventSystemFlags, nativeEvent);
}