React源码解析——事件系统
和Vue类似,React同样实现了一套管理事件的系统,不过两者的实现思想还是有着不小的区别。Vue为每个实例维护了一个事件管理对象,而React将所有事件都代理到document再进行分发。这篇文章就来详细分析React中管理事件的思路。
事件注册
在React中注册事件回调函数的方式大家应该都很熟悉,实际上就是通过props传递,那么首先对事件处理的方法也应该在处理props的逻辑里。即在组件挂载和更新时,会调用的_updateDOMProperties方法中。
ReactDOMComponent.Mixin = {
_updateDOMProperties: function (lastProps, nextProps, transaction) {
...
for (propKey in nextProps) {
if (registrationNameModules.hasOwnProperty(propKey)) {
enqueuePutListener(this, propKey, nextProp, transaction);
}
}
}
}
该方法中对每个props进行判断是否为事件属性,如果是,则进入到enqueuePutListener注册事件。
function enqueuePutListener(inst, registrationName, listener, transaction) {
if (transaction instanceof ReactServerRenderingTransaction) {
return;
}
var containerInfo = inst._hostContainerInfo;
var isDocumentFragment = containerInfo._node && containerInfo._node.nodeType === DOC_FRAGMENT_TYPE;
var doc = isDocumentFragment ? containerInfo._node : containerInfo._ownerDocument;
listenTo(registrationName, doc);
transaction.getReactMountReady().enqueue(putListener, {
inst: inst,
registrationName: registrationName,
listener: listener
});
}
该函数主要做了两件事:
- 通过listenTo函数将事件注册到原生DOM的document上。
- 通过事务队列的形式调用putListener函数将事件存储起来。
document注册事件分发回调
先看listenTo源码
// ReactBrowserEventEmitter.js
listenTo: function (registrationName, contentDocumentHandle) {
...
if (...) {
ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(...);
} else if (...) {
ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(...);
}
...
}
这段代码的主要目的是区分注册事件为捕获事件还是冒泡事件,并分别调用函数处理。再进入到两个处理函数:
// ReactEventListener.js
var ReactEventListener = {
...
trapBubbledEvent: function (topLevelType, handlerBaseName, element) {
...
var handler = ReactEventListener.dispatchEvent.bind(null, topLevelType);
return EventListener.listen(element, handlerBaseName, handler);
},
trapCapturedEvent: function (topLevelType, handlerBaseName, element) {
var handler = ReactEventListener.dispatchEvent.bind(null, topLevelType);
return EventListener.capture(element, handlerBaseName, handler);
}
dispatchEvent: function (topLevelType, nativeEvent) {
...
ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping);
...
}
}
两个处理函数都是通过EventListener的listen方法为document注册回调,listen方法主要是兼容性处理,源码如下。可以看到无论是什么事件都是注册的相同回调方法dispatchEvent,说明document上注册的回调只起到事件分发的作用,不包含具体的事件处理。
listen: function listen(target, eventType, callback) {
if (target.addEventListener) {
target.addEventListener(eventType, callback, false);
return {
remove: function remove() {
target.removeEventListener(eventType, callback, false);
}
};
} else if (target.attachEvent) {
target.attachEvent('on' + eventType, callback);
return {
remove: function remove() {
target.detachEvent('on' + eventType, callback);
}
};
}
}
那么既然事件的注册都是相同的分发回调,真正的回调函数又注册到哪了呢?这就是事务队列做的事了。
transaction存储事件处理回调
当事务处于close阶段时,将执行以上提到的putListener开启存储回调流程。
function putListener() {
var listenerToPut = this;
EventPluginHub.putListener(listenerToPut.inst, listenerToPut.registrationName, listenerToPut.listener);
}
该函数封装了一层代理,实际上调用了EventPluginHub上的putListener方法。
var listenerBank = {};
var getDictionaryKey = function (inst) {
return '.' + inst._rootNodeID;
}
var EventPluginHub = {
putListener: function (inst, registrationName, listener) {
...
var key = getDictionaryKey(inst);
var bankForRegistrationName = listenerBank[registrationName] || (listenerBank[registrationName] = {});
bankForRegistrationName[key] = listener;
...
}
}
事件存储管理对象为listerBank,按二维数组的结构对回调分类存储,两个key为React组件实例ID和事件类型。
到这一步就完成了React的事件注册了。
事件执行
由以上可知,所有事件的执行实际上都是调用了document上注册的dispatchEvent方法。而该方法也并没有什么关键逻辑,其任务是将事件分发的真正核心——handleTopLevelImpl方法放入批处理队列中。
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));
}
}
该方法首先找到事件触发的DOM和React组件,然后由触发组件向上遍历获取所有父组件ancestors数组,并按冒泡顺序调用_handleTopLevel方法处理真正的回调。
_handleTopLevel: function (topLevelType, targetInst, nativeEvent, nativeEventTarget) {
var events = EventPluginHub.extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget);
runEventQueueInBatch(events);
}
核心就是两件事:
- 调用extractEvents函数,根据原生事件构造出react合成事件
- 调用runEventQueueInBatch批处理队列中的合成事件
构造react合成事件
// EventPluginHub.js
var EventPluginHub = {
extractEvents: function (topLevelType, targetInst, nativeEvent, nativeEventTarget) {
var events;
var plugins = EventPluginRegistry.plugins;
for (var i = 0; i < plugins.length; i++) {
var possiblePlugin = plugins[i];
if (possiblePlugin) {
var extractedEvents = possiblePlugin.extractEvents(topLevelType, targetInst, nativeEvent, nativeEventTarget);
if (extractedEvents) {
events = accumulateInto(events, extractedEvents);
}
}
}
return events;
},
}
EventPluginHub的作用除了存储事件的回调函数之外,还存储着五种事件plugin用来合成事件。这些plugin在react初始化时就完成了注入。
// ReactDOM.js
var ReactDefaultInjection = require('./ReactDefaultInjection');
ReactDefaultInjection.inject();
...
// ReactDefaultInjection.js
module.exports = {
inject: inject
};
function inject() {
...
ReactInjection.EventPluginHub.injectEventPluginsByName({
SimpleEventPlugin: SimpleEventPlugin,
EnterLeaveEventPlugin: EnterLeaveEventPlugin,
ChangeEventPlugin: ChangeEventPlugin,
SelectEventPlugin: SelectEventPlugin,
BeforeInputEventPlugin: BeforeInputEventPlugin
});
...
}
以基础事件对应的Plugin:SimpleEventPlugin为例来分析合成事件过程:
var SimpleEventPlugin = {
extractEvents: function (topLevelType, ...) {
var EventConstructor;
switch (topLevelType) {
EventConstructor = one of [ SyntheticEvent, SyntheticKeyboardEvent, SyntheticFocusEvent, SyntheticMouseEvent, SyntheticDragEvent, SyntheticTouchEvent, SyntheticAnimationEvent, SyntheticTransitionEvent, SyntheticUIEvent, SyntheticWheelEvent, SyntheticClipboardEvent];
}
var event = EventConstructor.getPooled(dispatchConfig, targetInst, nativeEvent, nativeEventTarget);
EventPropagators.accumulateTwoPhaseDispatches(event);
return event;
}
}
在Plugin进一步对事件类型进行细分,再根据细分类型从对象池中取出对象合成事件。
对象池是React事件的性能优化的一个关键,其关键思想在于当类的实例化开销较大并且可重用时,预先初始化好对象集合并维护,可以减少频繁创建和销毁对象的成本,实现缓存和复用。
下面为基本细分合成事件SyntheticEvent的源码:
function SyntheticEvent(dispatchConfig, targetInst, nativeEvent, nativeEventTarget) {
...
this.dispatchConfig = dispatchConfig;
this._targetInst = targetInst;
this.nativeEvent = nativeEvent;
var Interface = this.constructor.Interface;
for (var propName in Interface) {
var normalize = Interface[propName];
if (normalize) {
this[propName] = normalize(nativeEvent);
} else {
if (propName === 'target') {
this.target = nativeEventTarget;
} else {
this[propName] = nativeEvent[propName];
}
}
}
...
}
_assign(SyntheticEvent.prototype, {
preventDefault: function () { ... },
stopPropagation: function () { ... },
...
});
var EventInterface = {
type: null,
target: null,
currentTarget: emptyFunction.thatReturnsNull,
eventPhase: null,
bubbles: null,
cancelable: null,
timeStamp: function (event) {
return event.timeStamp || Date.now();
},
defaultPrevented: null,
isTrusted: null
};
SyntheticEvent.Interface = EventInterface;
SyntheticEvent.augmentClass = function (Class, Interface) {
...
}
获取合成事件后,再通过accumulateTwoPhaseDispatches方法从EventPluginHub中获取对应的回调函数。
// EventPropagators.js
function accumulateTwoPhaseDispatches(events) {
forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle);
}
function accumulateTwoPhaseDispatchesSingle(event) {
if (event && event.dispatchConfig.phasedRegistrationNames) {
EventPluginUtils.traverseTwoPhase(event._targetInst, accumulateDirectionalDispatches, event);
}
}
function accumulateDirectionalDispatches(inst, phase, event) {
var listener = listenerAtPhase(inst, event, phase);
if (listener) {
event._dispatchListeners = accumulateInto(event._dispatchListeners, listener);
event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
}
}
var getListener = EventPluginHub.getListener;
function listenerAtPhase(inst, event, propagationPhase) {
var registrationName = event.dispatchConfig.phasedRegistrationNames[propagationPhase];
return getListener(inst, registrationName);
}
// EventPluginHub.js
getListener: function (inst, registrationName) {
var bankForRegistrationName = listenerBank[registrationName];
var key = getDictionaryKey(inst);
return bankForRegistrationName && bankForRegistrationName[key];
}
至此就完成了合成事件的生成,接下来就是批处理合成事件了。
批处理合成事件
批处理入口函数为runEventQueueInBatch
function runEventQueueInBatch(events) {
EventPluginHub.enqueueEvents(events);
EventPluginHub.processEventQueue(false);
}
enqueueEvents: function (events) {
if (events) {
eventQueue = accumulateInto(eventQueue, events);
}
},
processEventQueue: function (simulated) {
var processingEventQueue = eventQueue;
eventQueue = null;
if (simulated) {
forEachAccumulated(processingEventQueue, executeDispatchesAndReleaseSimulated);
} else {
forEachAccumulated(processingEventQueue, executeDispatchesAndReleaseTopLevel);
}
ReactErrorUtils.rethrowCaughtError();
},
先将待处理的新合成事件队列放入eventQueue中,再执行其中的事件,队列中也可能包含之前未处理的事件。
simulated标记用来判断是否为react测试代码,一般为false。
针对单个事件的处理入口函数为 executeDispatchesAndReleaseTopLevel。
var executeDispatchesAndReleaseTopLevel = function (e) {
return executeDispatchesAndRelease(e, false);
};
var executeDispatchesAndRelease = function (event, simulated) {
if (event) {
EventPluginUtils.executeDispatchesInOrder(event, simulated);
if (!event.isPersistent()) {
event.constructor.release(event);
}
}
};
该函数先调用核心处理函数executeDispatchesInOrde处理完事件后,再将事件release掉减少内存开销。
function executeDispatchesInOrder(event, simulated) {
var dispatchListeners = event._dispatchListeners;
var dispatchInstances = event._dispatchInstances;
if (Array.isArray(dispatchListeners)) {
for (var i = 0; i < dispatchListeners.length; i++) {
if (event.isPropagationStopped()) {
break;
}
executeDispatch(event, simulated, dispatchListeners[i], dispatchInstances[i]);
}
} else if (dispatchListeners) {
executeDispatch(event, simulated, dispatchListeners, dispatchInstances);
}
event._dispatchListeners = null;
event._dispatchInstances = null;
}
先取得JSX声明的回调和对应的React组件,再遍历实现类冒泡执行回调,当遇到阻止冒泡的属性时break掉循环,即阻止了冒泡。executeDispatch就是最终的回调执行函数了。
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) {
try {
func(a);
} catch (x) {
if (caughtError === null) {
caughtError = x;
}
}
}
小结
相比Vue事件系统的观察者模式,React的事件系统更加突出了中介者模式。可以看到整套系统的核心流程在于以下几点:
- Document元素作为中介,注册所有事件的分发处理函数。
- EventPluginHub作为事件的管理hub,存储着所有真实回调并初始化合成事件处理对象池。
- 事件触发时通过伪造冒泡收集所有事件触发组件和事件类型并在EventPluginHub合成事件对象
- 批处理合成事件
-- EOF --