上文仿制事件流1.我们说到了下边的方法。
ReactEventListener._handleTopLevel(
bookKeeping.topLevelType,
topLevelTarget,
topLevelTargetID,
bookKeeping.nativeEvent,
getEventTarget(bookKeeping.nativeEvent)
);
先来一段简洁的介绍
ReactEventListener._handleTopLevel 方法最终是调用了
ReactEventEmitterMixin.handleTopLevel 方法。
而ReactEventEmitterMixin.handleTopLevel 方法是调用 EventPluginHub.extractEvents 方法生成合成事件,
而后将合成事件放入事件队列里执行。
而关于生成合成事件:
EventPluginHub.extractEvents 方法会选择不同的事件插件进行处理生成合成事件。
比如调用 simpleEventPlugin 的 extractEvents 方法,而
simpleEventPlugin 的 extractEvents 方法的核心是调用了
EventPropagators.accumulateTwoPhaseDispatches 方法。
而 EventPropagators.accumulateTwoPhaseDispatches 方法则是调用了
该模块下的 accumulateTwoPhaseDispatches。
而 accumulateTwoPhaseDispatches 方法则是调用了
EventPluginHub.injection.getInstanceHandle().traverseTwoPhase()方法。
实则是调用了 ReactInstanceHandles模块的 traverseTwoPhase() 方法,
traverseTwoPhase 方法则是根据给的事件触发的id来执行两个方法。
ReactInstanceHandles 模块的下的
traverseParentPath('', targetID, cb, arg, true, false);
该函数的主要作用是计算从React根组件到触发事件的这个组件之间的所经过的全部React组件
而后主要是调用了一个方法。EventPropagators模块下的 accumulateDirectionalDispatches 方法。
而 EventPropagators.accumulateDirectionalDispatches 方法的主要作用是 根据捕获事件流所经过的全部的
React组件的id找到对应事件的侦听器。主要是从 listenerBank 中取出来,而后放入源事件的属性中。
如下:
event._dispatchListeners = accumulateInto(event._dispatchListeners, listener);
event._dispatchIDs = accumulateInto(event._dispatchIDs, domID);
traverseParentPath(targetID, '', cb, arg, false, true);
这个方法只是冒泡流的机制来找到所有的侦听器。
最后将合成事件放入队列中,而后一个个的去执行这些事件。
用到的方法是。
入队
EventPluginHub.enqueueEvents(events);
执行队列
EventPluginHub.processEventQueue(false);
精华以上,糟粕其下。接下来我们一个个的说,
ReactEventListener._handleTopLevel方法是调用了下边的这个方法。
var ReactEventEmitterMixin = {
<!--
先看看参数:
topLevelType:就是触发的事件类型在document上的表示 onClick表示为topClick。
topLevelTarget:触发事件的源元素(nodeType为1的元素。如果是text_node触发的事件,那就向上找到它的父元素作为触发事件的源元素,Safari中会有这种情况)
topLevelTargetID:触发事件的元素的id
nativeEvent:触发事件的源事件对象。
nativeEventTarget:和topLevelTarget应该是一个东西
-->
handleTopLevel: function (topLevelType, topLevelTarget, topLevelTargetID, nativeEvent, nativeEventTarget) {
<!--
首先是调用了 EventPluginHub.extractEvents来生成合成事件。
-->
var events = EventPluginHub.extractEvents(topLevelType, topLevelTarget, topLevelTargetID, nativeEvent, nativeEventTarget);
<!--
将合成的事件放入队列里,而后执行。
-->
runEventQueueInBatch(events);
<!--
runEventQueueInBatch方法如下。
function runEventQueueInBatch(events) {
EventPluginHub.enqueueEvents(events);
EventPluginHub.processEventQueue(false);
}
-->
}
};
到了这里我们将合成事件放入了一个事件队列里,而后执行这个队列。但是,没有接触到关于事件流的情形。如果一个元素有一个click事件,其父元素也有click事件,那么React是如何在元素事件触发的时候也触发其父元素的click事件。
handleTopLevel方法的核心是调用了EventPluginHub模块的extractEvents方法,我们来看一下EventPluginHub.extractEvents()方法。
EventPluginHub.extractEvents()方法。
extractEvents: function (topLevelType, topLevelTarget, topLevelTargetID, nativeEvent, nativeEventTarget) {
var events;
var plugins = EventPluginRegistry.plugins;
for (var i = 0; i < plugins.length; i++) {
var possiblePlugin = plugins[i];
<!--
plugins内的内容为默认的事件插件,用来处理原生事件为合成事件,这些插件也是通过注入机制注入进来的
[
SimpleEventPlugin: SimpleEventPlugin,
EnterLeaveEventPlugin: EnterLeaveEventPlugin,
ChangeEventPlugin: ChangeEventPlugin,
SelectEventPlugin: SelectEventPlugin,
BeforeInputEventPlugin: BeforeInputEventPlugin
]
-->
if (possiblePlugin) {
// 使用提供的事件插件来提取合成事件
var extractedEvents = possiblePlugin.extractEvents(topLevelType, topLevelTarget, topLevelTargetID, nativeEvent, nativeEventTarget);
if (extractedEvents) {
events = accumulateInto(events, extractedEvents);
}
}
}
return events;
},
我们以simpleEventPlugin为例来说一下事件插件:
extractEvents: function (topLevelType, topLevelTarget, topLevelTargetID, nativeEvent, nativeEventTarget) {
var dispatchConfig = topLevelEventsToDispatchConfig[topLevelType];
<!--
dispatchConfig如下:
{
dependencies: ["topClick"]
phasedRegistrationNames:{
bubbled: "onClick"
captured: "onClickCapture"
}
}
-->
<!--
我们假设现在触发的事件是click事件,topLevelType为topClick。React会根据事件类型选择事件插件
-->
switch (topLevelType) {
case topLevelTypes.topClick:
EventConstructor = SyntheticMouseEvent;
break;
}
<!--
使用池化技术来使用EventConstructor生成一个event。
而EventConstructor则是 SyntheticMouseEvent 类(叫做鼠标合成事件插件)
-->
var event = EventConstructor.getPooled(dispatchConfig, topLevelTargetID, nativeEvent, nativeEventTarget); -----------(1)
<!--
这个方法就是用来收集捕获事件流和冒泡事件流的事件。
-->
EventPropagators.accumulateTwoPhaseDispatches(event);--------(2)
return event;
},
这里提一句,合成事件的作用主要是为了抹平不同的浏览器之间的差异,同样的click事件在Chrome,Safari,Opera,IE等的浏览器中的表现是不一样的,而React提供了一个中间层 SyntheticEvent来将事件处理为统一样子,比如提供一个统一的阻止默认事件的函数,或者是阻止冒泡的函数等,如此不同的浏览器事件经过处理定义为拥有同样的属性的对象,最后会将原生事件对象也放在其中,这就形成了一个合成事件。React对于一个事件会使用默认的五个事件插件来进行处理,最终会将其合成为一个合成事件的数组。
我们一个个来看核心代码,首先是 (1):
<!--
首先我们假设是topClick事件,那么EventConstructor就是 SyntheticMouseEvent。
SyntheticMouseEvent 是主要用来处理鼠标事件的事件插件。
下边的代码的作用是启用池化技术初始化一个 SyntheticMouseEvent 实例。我们先不管池化技术,只简单的认为是初始化了一个 SyntheticMouseEvent 实例,
-->
var event = EventConstructor.getPooled(dispatchConfig, topLevelTargetID, nativeEvent, nativeEventTarget);
分割线
==========================================================
来看一下 SyntheticMouseEvent 类。
<!--
如下是 SyntheticMouseEvent 抹平不同的浏览器的鼠标事件所做的,定义了一些属性。
MouseEventInterface是一个接口 Interface。
-->
var MouseEventInterface = {
screenX: null,
screenY: null,
clientX: null,
clientY: null,
ctrlKey: null,
shiftKey: null,
altKey: null,
metaKey: null,
getModifierState: getEventModifierState,
button: function (event) {
// Webkit, Firefox, IE9+
// which: 1 2 3
// button: 0 1 2 (standard)
var button = event.button;
if ('which' in event) {
return button;
}
// IE<9
// which: undefined
// button: 0 0 0
// button: 1 4 2 (onmouseup)
return button === 2 ? 2 : button === 4 ? 1 : 0;
},
buttons: null,
relatedTarget: function (event) {
return event.relatedTarget || (event.fromElement === event.srcElement ? event.toElement : event.fromElement);
},
// "Proprietary" Interface.
pageX: function (event) {
return 'pageX' in event ? event.pageX : event.clientX + ViewportMetrics.currentScrollLeft;
},
pageY: function (event) {
return 'pageY' in event ? event.pageY : event.clientY + ViewportMetrics.currentScrollTop;
}
};
'ok 到此,我们便先打住,以后再来探究React是如何生成合成事件的。'
继续看核心代码其 (2):
<!--
这里就是收集事件的传播过程。捕获事件流和冒泡事件流。
-->
EventPropagators.accumulateTwoPhaseDispatches(event);
先看看 EventPropagators 模块:
<!--
EventPropagators.accumulateTwoPhaseDispatches方法是调用了如下方法。
forEachAccumulated方法的作用是判断第一个参数如果是数组
那就循环执行第二个参数(这个参数是方法)
如果不是函数,直接就以第一个参数作为第二个参数的参数,执行第二个参树。
总之就是下边的方法会执行第二个参数。
-->
function accumulateTwoPhaseDispatches(events) {
forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle);
}
分割线
==========================================================
<!--
accumulateTwoPhaseDispatchesSingle方法如下。
-->
function accumulateTwoPhaseDispatchesSingle(event) {
if (event && event.dispatchConfig.phasedRegistrationNames) {
EventPluginHub.injection.getInstanceHandle().traverseTwoPhase(event.dispatchMarker, accumulateDirectionalDispatches, event);
}
}