Механизм событий React по-прежнему очень интересен, а идея симуляции доставки событий и использования document для делегирования большинства событий более интересна.
Блок-схема механизма событий
анализ кода
(код содержит только часть с параметрами события)
_updateDOMProperties — это точка входа для обработки параметров событий, просто обратите внимание на метод enqueuePutListener, который является функцией входа для регистрации событий. Переменная RegistrationNameModules содержит объект сопоставления типов событий и соответствующих методов, как показано на рисунке:
Инициализация этих сопоставлений объясняется в разделе «Анализ исходного кода React — начальный рендеринг компонента».
_updateDOMProperties: function (lastProps, nextProps, transaction) {
var propKey;
var styleName;
var styleUpdates;
for (propKey in lastProps) {
if (nextProps.hasOwnProperty(propKey) || !lastProps.hasOwnProperty(propKey) || lastProps[propKey] == null) {
continue;
}
if (registrationNameModules.hasOwnProperty(propKey)) {
if (lastProps[propKey]) {
// Only call deleteListener if there was a listener previously or
// else willDeleteListener gets called when there wasn't actually a
// listener (e.g., onClick={null})
deleteListener(this, propKey);
}
}
}
for (propKey in nextProps) {
var nextProp = nextProps[propKey];
var lastProp = propKey === STYLE ? this._previousStyleCopy : lastProps != null ? lastProps[propKey] : undefined;
if (!nextProps.hasOwnProperty(propKey) || nextProp === lastProp || nextProp == null && lastProp == null) {
continue;
}
if (registrationNameModules.hasOwnProperty(propKey)) { // 处理事件参数。
if (nextProp) {
enqueuePutListener(this, propKey, nextProp, transaction); // 注册事件,委托到属于的document上
} else if (lastProp) {
deleteListener(this, propKey);
}
}
}
}
enqueuePutListener
- listenTo
- putListener
function enqueuePutListener(inst, registrationName, listener, transaction) {
var containerInfo = inst._nativeContainerInfo;
var doc = containerInfo._ownerDocument; // 大部分的事件都被到对应的document上
if (!doc) { // ssr
// Server rendering.
return;
}
listenTo(registrationName, doc);
transaction.getReactMountReady().enqueue(putListener, {
inst: inst,
registrationName: registrationName,
listener: listener
});
}
listenToЭто метод делегирования событий документу, и большинство событий делегируются документу. Но из-за ограничения типа событий, которые можно поймать в документе (Document Object Model Events), не все типы событий делегируются документу, а некоторые напрямую делегируются самому элементу.
putListenerДобавьте соответствующий тип события, целевой объект события и метод, который будет выполняться при запуске события, в объект listenerBank.
listenTo: function (registrationName, contentDocumentHandle) {
var mountAt = contentDocumentHandle;
var isListening = getListeningForDocument(mountAt);
var dependencies = EventPluginRegistry.registrationNameDependencies[registrationName];
var topLevelTypes = EventConstants.topLevelTypes;
for (var i = 0; i < dependencies.length; i++) {
var dependency = dependencies[i];
if (!(isListening.hasOwnProperty(dependency) && isListening[dependency])) {
// 先判断先几个需要特殊处理的事件,主要都是兼容性的原因。
if (...) {
......
} else if (topEventMapping.hasOwnProperty(dependency)) {
ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(dependency, topEventMapping[dependency], mountAt);
}
isListening[dependency] = true;
}
}
}
// 冒泡阶段的触发的事件的委托
trapBubbledEvent: function (topLevelType, handlerBaseName, handle) {
return ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(topLevelType, handlerBaseName, handle);
},
// 捕获阶段的触发的事件的委托
trapCapturedEvent: function (topLevelType, handlerBaseName, handle) {
return ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(topLevelType, handlerBaseName, handle);
},
trapBubbledEvent: function (topLevelType, handlerBaseName, handle) {
return EventListener.listen(element, handlerBaseName, ReactEventListener.dispatchEvent.bind(null, topLevelType));
},
trapCapturedEvent: function (topLevelType, handlerBaseName, handle) {
return EventListener.capture(element, handlerBaseName, ReactEventListener.dispatchEvent.bind(null, topLevelType));
},
listen: function listen(target, eventType, callback) {
if (target.addEventListener) {
target.addEventListener(eventType, callback, false);
}
},
capture: function capture(target, eventType, callback) {
if (target.addEventListener) {
target.addEventListener(eventType, callback, true);
}
},
Дело в том, что все обратные вызовы делегированных событий — это ReactEventListener.dispatchEvent.
dispatchEvent: function (topLevelType, nativeEvent) {
// bookKeeping的初始化使用了react在源码中用到的对象池的方法来避免多余的垃圾回收。
// bookKeeping的作用看ta的定义就知道了,就是一个用来保存过程中会使用到的变量的对象。
var bookKeeping = TopLevelCallbackBookKeeping.getPooled(topLevelType, nativeEvent);
try {
ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping);
} finally {
TopLevelCallbackBookKeeping.release(bookKeeping);
}
}
Метод handleTopLevelImpl проходит через объект триггера события и его родительские элементы (доставка события) и выполняет метод _handleTopLevel для каждого элемента.
function handleTopLevelImpl(bookKeeping) {
var nativeEventTarget = getEventTarget(bookKeeping.nativeEvent);
var targetInst = ReactDOMComponentTree.getClosestInstanceFromNode(nativeEventTarget);
var ancestor = targetInst;
do {
bookKeeping.ancestors.push(ancestor);
ancestor = ancestor && findParent(ancestor);
} while (ancestor);
for (var i = 0; i < bookKeeping.ancestors.length; i++) {
targetInst = bookKeeping.ancestors[i];
ReactEventListener._handleTopLevel(bookKeeping.topLevelType, targetInst, bookKeeping.nativeEvent, getEventTarget(bookKeeping.nativeEvent));
}
}
handleTopLevel извлекает все события, которые необходимо выполнить, и соответствующие функции обратного вызова в соответствии с объектом события и типом инициированного события, которые единообразно выполняются runEventQueueInBatch.
handleTopLevel: function (topLevelType, targetInst, nativeEvent, nativeEventTarget) {
var events = EventPluginHub.extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget);
runEventQueueInBatch(events);
}
Метод ExtractEvents вызывает метод ExtractEvents соответствующего подключаемого модуля, чтобы получить события, которые необходимо выполнить для соответствующего типа подключаемого модуля, а затем объединить их вместе.
extractEvents: function (topLevelType, targetInst, nativeEvent, nativeEventTarget) {
var events;
var plugins = EventPluginRegistry.plugins;
for (var i = 0; i < plugins.length; i++) {
// Not every plugin in the ordering may be loaded at runtime.
var possiblePlugin = plugins[i];
if (possiblePlugin) {
var extractedEvents = possiblePlugin.extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget);
if (extractedEvents) {
events = accumulateInto(events, extractedEvents);
}
}
}
return events;
}
Интересная вещь о методе ExtractEvents плагина заключается в том, чтоEventPropagators.accumulateTwoPhaseDispatches(event).
EventPropagators.accumulateTwoPhaseDispatches имитирует процесс доставки события: захват -> цель -> пузырь, и возвращает все функции обратного вызова и соответствующие элементы, соответствующие типу события на этом пути, в порядке доставки события.
(картинка изEvent dispatch and DOM event flow)
function traverseTwoPhase(inst, fn, arg) {
var path = [];
while (inst) {
path.push(inst);
inst = inst._nativeParent;
}
var i;
for (i = path.length; i-- > 0;) {
fn(path[i], false, arg);
}
for (i = 0; i < path.length; i++) {
fn(path[i], true, arg);
}
}
Метод traverseTwoPhase имитирует процесс доставки события и получает соответствующую функцию обратного вызова и объект события и сохраняет их в _dispatchListeners и _dispatchInstances объекта события, синтезированного реагирующим
function accumulateDirectionalDispatches(inst, upwards, event) {
var phase = upwards ? PropagationPhases.bubbled : PropagationPhases.captured;
var listener = listenerAtPhase(inst, event, phase);
if (listener) {
// event._dispatchListeners结果就是这个event在event flow的过程中会触发那些listenter的callback【按照event flow的顺序push到一个数组中了】
event._dispatchListeners = accumulateInto(event._dispatchListeners, listener);
event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
}
}
Прослушиватель запроса и соответствующий inst используют тип события и _rootNodeID, ListenerBank хранит функцию обратного вызова, соответствующую элементу под типом:
function listenerAtPhase(inst, event, propagationPhase) {
var registrationName = event.dispatchConfig.phasedRegistrationNames[propagationPhase];
return getListener(inst, registrationName);
}
getListener: function (inst, registrationName) {
var bankForRegistrationName = listenerBank[registrationName];
return bankForRegistrationName && bankForRegistrationName[inst._rootNodeID];
},
Генерация содержимого listenerBank выполняется вторым основным методом putListener, упомянутым ранее.
putListenerСпособ использования транзакций унифицирован на этапе ReactMountReady.
putListener: function (inst, registrationName, listener) {
var bankForRegistrationName = listenerBank[registrationName] || (listenerBank[registrationName] = {});
bankForRegistrationName[inst._rootNodeID] = listener;
}
После того, как в ExtractEvents появятся события, соответствующие сработавшему типу события, поместите все искусственные события в очередь событий через runEventQueueInBatch(events), а вторым шагом будет выполнение их по одному.
function runEventQueueInBatch(events) {
EventPluginHub.enqueueEvents(events);
EventPluginHub.processEventQueue(false);
}
function executeDispatchesInOrder(event, simulated) {
var dispatchListeners = event._dispatchListeners;
var dispatchInstances = event._dispatchInstances;
if (process.env.NODE_ENV !== 'production') {
validateEventDispatches(event);
}
if (Array.isArray(dispatchListeners)) {
for (var i = 0; i < dispatchListeners.length; i++) {
if (event.isPropagationStopped()) {
break;
}
// Listeners and Instances are two parallel arrays that are always in sync.
executeDispatch(event, simulated, dispatchListeners[i], dispatchInstances[i]);
}
} else if (dispatchListeners) {
executeDispatch(event, simulated, dispatchListeners, dispatchInstances);
}
event._dispatchListeners = null;
event._dispatchInstances = null;
}
function executeDispatch(event, simulated, listener, inst) {
var type = event.type || 'unknown-event';
event.currentTarget = EventPluginUtils.getNodeFromInstance(inst);
if (simulated) {
ReactErrorUtils.invokeGuardedCallbackWithCatch(type, listener, event);
} else {
ReactErrorUtils.invokeGuardedCallback(type, listener, event);
}
event.currentTarget = null;
}
function invokeGuardedCallback(name, func, a, b) {
try {
return func(a, b);
} catch (x) {
if (caughtError === null) {
caughtError = x;
}
return undefined;
}
}
Суммировать
- Единая диспетчерская функция dispatchEvent.
- Объект события React является синтетическим объектом (SyntheticEvent).
- Почти все события делегируются документированию в целях оптимизации производительности.
- При смешивании синтетических событий и нативных событий следует отметить, что события React в основном делегируются документам.
использованная литература