前言
本篇文章为上文<一看就懂的React事件机制>附带的小知识
合成事件
EventPluginHub
在初始化的时候,注入了七个plugin, 它们是DefaultEventPluginOrder.js
里的
var DefaultEventPluginOrder = ['ResponderEventPlugin', 'SimpleEventPlugin', 'TapEventPlugin', 'EnterLeaveEventPlugin', 'ChangeEventPlugin', 'SelectEventPlugin', 'BeforeInputEventPlugin'];
其中我们最常用到的就是SimpleEventPlugin
。所以这里用SimpleEventPlugin
来分析。
SimpleEventPlugin
// 一开始先生成dispatchConfig,注释也写的比较清楚了
/**
* Turns
* ['abort', ...]
* into
* eventTypes = {
* 'abort': {
* phasedRegistrationNames: {
* bubbled: 'onAbort',
* captured: 'onAbortCapture',
* },
* dependencies: ['topAbort'],
* },
* ...
* };
* topLevelEventsToDispatchConfig = {
* 'topAbort': { sameConfig }
* };
*/
var eventTypes = {};
var topLevelEventsToDispatchConfig = {};
['abort', 'animationEnd', 'animationIteration', 'animationStart', 'blur', 'canPlay', 'canPlayThrough', 'click', 'contextMenu', 'copy', 'cut', 'doubleClick', 'drag', 'dragEnd', 'dragEnter', 'dragExit', 'dragLeave', 'dragOver', 'dragStart', 'drop', 'durationChange', 'emptied', 'encrypted', 'ended', 'error', 'focus', 'input', 'invalid', 'keyDown', 'keyPress', 'keyUp', 'load', 'loadedData', 'loadedMetadata', 'loadStart', 'mouseDown', 'mouseMove', 'mouseOut', 'mouseOver', 'mouseUp', 'paste', 'pause', 'play', 'playing', 'progress', 'rateChange', 'reset', 'scroll', 'seeked', 'seeking', 'stalled', 'submit', 'suspend', 'timeUpdate', 'touchCancel', 'touchEnd', 'touchMove', 'touchStart', 'transitionEnd', 'volumeChange', 'waiting', 'wheel'].forEach(function (event) {
var capitalizedEvent = event[0].toUpperCase() + event.slice(1);
var onEvent = 'on' + capitalizedEvent;
var topEvent = 'top' + capitalizedEvent;
var type = {
phasedRegistrationNames: {
bubbled: onEvent,
captured: onEvent + 'Capture'
},
dependencies: [topEvent]
};
eventTypes[event] = type;
topLevelEventsToDispatchConfig[topEvent] = type;
});
// 重点是extractEvents函数,用它生成一个合成事件,每个plugin都一定要有这个函数
var SimpleEventPlugin = {
...,
extractEvents:function (topLevelType, targetInst, nativeEvent, nativeEventTarget) {
var dispatchConfig = topLevelEventsToDispatchConfig[topLevelType];
if (!dispatchConfig) {
return null;
}
var EventConstructor;
switch (topLevelType) {
...
case 'topClick':
// Firefox creates a click event on right mouse clicks. This removes the
// unwanted click events.
if (nativeEvent.button === 2) {
return null;
}
/* falls through */
case 'topDoubleClick':
case 'topMouseDown':
case 'topMouseMove':
case 'topMouseUp':
// TODO: Disabled elements should not respond to mouse events
/* falls through */
case 'topMouseOut':
case 'topMouseOver':
case 'topContextMenu':
// 有这里可以看到onClick使用的构造函数是SyntheticMouseEvent
EventConstructor = SyntheticMouseEvent;
break;
...
// 从对象池中取出这个event的一个instance,对象池的概念是为了节省内存,
// 这里不做重点了解,不了解的朋友可以这么理解,这里返回了一个
// new EventConstructor()的实例
var event = EventConstructor.getPooled(dispatchConfig, targetInst, nativeEvent, nativeEventTarget);
EventPropagators.accumulateTwoPhaseDispatches(event);
return event;
}
}
然后一步步顺藤摸瓜
EventPropagators.js
function accumulateTwoPhaseDispatches(events) {
forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle);
}
forEachAccumulated
这个功能函数在文章的开头讲过,忘记了朋友可以回去看看,其实就是当event不是数组的时候,直接调用accumulateTwoPhaseDispatchesSingle,参数为events。
function accumulateTwoPhaseDispatchesSingle(event) {
if (event && event.dispatchConfig.phasedRegistrationNames) {
// 这里有个accumulateDirectionalDispatches放到文章后面讲解
EventPluginUtils.traverseTwoPhase(event._targetInst, accumulateDirectionalDispatches, event);
}
}
EventPluginUtils.js
traverseTwoPhase: function (target, fn, arg) {
return TreeTraversal.traverseTwoPhase(target, fn, arg);
},
ReactDomTreeTraversal.js
/**
* Simulates the traversal of a two-phase, capture/bubble event dispatch.
*/
function traverseTwoPhase(inst, fn, arg) {
var path = [];
while (inst) {
path.push(inst);
inst = inst._hostParent;
}
var i;
for (i = path.length; i-- > 0;) {
// 这里从数组的后面开始循环调用fn,这么做是捕获的顺序,这样外层的函数绑定的事件就会被先执行
fn(path[i], 'captured', arg);
}
for (i = 0; i < path.length; i++) {
// 然后在从数组的前面循环调用,这么做是冒泡的顺序
fn(path[i], 'bubbled', arg);
}
}
上文traverseTwoPhase
里的fn
其实就是EventPropagator.js 的accumulateDirectionalDispatches,接下来让我们看看这个函数做了什么
EventPropagator.js
// 这个函数的作用是给合成事件加上listener,最终所有同类型的listener都会放到_dispatchListeners里,
function accumulateDirectionalDispatches(inst, phase, event) {
if (process.env.NODE_ENV !== 'production') {
process.env.NODE_ENV !== 'production' ? warning(inst, 'Dispatching inst must not be null') : void 0;
}
// 根据事件阶段的不同取出响应的事件
var listener = listenerAtPhase(inst, event, phase);
if (listener) {
// accumulateInto在文章的最开始讲过,这里将所有的listener都存入_dispatchListeners中
// 本文中_dispatchListeners = [onClick, outClick]
event._dispatchListeners = accumulateInto(event._dispatchListeners, listener);
event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
}
}
下面来看看取出响应事件的过程:
/**
* Some event types have a notion of different registration names for different
* "phases" of propagation. This finds listeners by a given phase.
*/
// 找到不同阶段(捕获/冒泡)元素绑定的回调函数 listener
function listenerAtPhase(inst, event, propagationPhase) {
var registrationName = event.dispatchConfig.phasedRegistrationNames[propagationPhase];
return getListener(inst, registrationName);
}
还记得我们前面在事件注册的时候,用putListener
把listener
存进listenerBank[registrationName][key]
么,这里的getListener
用于取出我们之前存放的回调函数.
EventPluginHub.js
/**
* @param {object} inst The instance, which is the source of events.
* @param {string} registrationName Name of listener (e.g. `onClick`).
* @return {?function} The stored callback.
*/
getListener: function (inst, registrationName) {
// TODO: shouldPreventMouseEvent is DOM-specific and definitely should not
// live here; needs to be moved to a better place soon
var bankForRegistrationName = listenerBank[registrationName];
if (shouldPreventMouseEvent(registrationName, inst._currentElement.type, inst._currentElement.props)) {
return null;
}
var key = getDictionaryKey(inst);
return bankForRegistrationName && bankForRegistrationName[key];
},
以上,就是生成合成事件的过程,这里有个重中之中就是合成事件收集了一波同类型例如click的回调函数存在了event._dispatchListeners里。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。