背景
关于事件,相信都不陌生。 我们再DOM上绑定事件,并给定一个处理函数。在DOM上事件触发的时候,会执行处理函数。
当然也有另外一种事件绑定机制---代理。
一般我们再用ul元素的时候,我们的事件一般是绑定在li的父元素ul上,这样让ul来代理li的事件,这样只用绑定一次即可,而React的事件也是使用的代理模式,但是,React的所有事件都是绑定在document上的。
React的事件机制分为两个模块
注册与存储
分发事件
我们先来说说 事件的注册与存储
事件的注册与存储也分为两个模块,注册与回调函数存储。
入口 ReactDOMComponent模块
enqueuePutListener 方法是整个React事件机制的核心方法。这个方法的核心作用是注册事件到document上,与存储回调函数到EventPluginHub中
/*
@param id: 事件绑定的当前元素的id
@param registrationName: 绑定的事件名称,onClick。
@param listener: onClick后的那个回调函数
@param transaction: 一个事务
*/
function enqueuePutListener(id, registrationName, listener, transaction) {
// id是当前元素的id,就是你绑定的事件,比如onClick绑定在哪个元素上,那个元素的data-reactid属性的值。
var container = ReactMount.findReactContainerForID(id);
// container就是包裹所有React组件的那个元素。单页面应用一般只有一个 就是我们在HTML中定义的那个 <div id="app"></div>
if (container) {
var doc = container.nodeType === ELEMENT_NODE_TYPE ? container.ownerDocument : container;
// doc是一个document
listenTo(registrationName, doc);
// 调用listenTo方法注册事件。注意,这里只是将事件注册在了document上,而没有给我们定义的回到函数。
// listenTo方法是ReactBrowserEventEmitter模块的listenTo方法。
}
// 使用事务,将listenr存储起来。
transaction.getReactMountReady().enqueue(putListener, {
id: id,
registrationName: registrationName,
listener: listener
});
}
enqueuePutListener方法的核心有两个
1 listenTo方法: ReactBrowserEventEmitter.listenTo主要的作用便是注册事件到document上。
2 enqueue方法:主要的作用便是将回调函数存储起来。
限于篇幅的作用,我们先来说说注册事件到document上的这条路。也就是方法 listenTo
ReactBrowserEventEmitter模块
listenTo: function (registrationName, contentDocumentHandle) {
<!--listenTo方法,考虑了太多的可能性,我们删除一些无用的代码,保留核心代码-->
<!--mountAt就是事件注册的document(页面中可以有多个document)-->
var mountAt = contentDocumentHandle;
<!--// isListening就是当前Document---mountAt,对应的一个唯一的对象。-->
var isListening = getListeningForDocument(mountAt);
<!--定义我们写入的事件与top事件的对应。 onAbort:['topAbort'],dependencies对应的是['topAbort']-->
<!--这里的top事件就是EventConstants模块中定义的,这些top事件是我们原始的事件类型,在顶层的一种表示方法。只是换了个名字而已。就像是click事件,我们绑定的时候是写作onClick的,但是,React要使用顶层来代理这些事件,所以就写作了topClick。-->
var dependencies = EventPluginRegistry.registrationNameDependencies[registrationName];
<!--EventPluginRegistry模块的registrationNameDependencies属性是一个对象,主要是保存了我们写作的事件与顶层事件的一一对应关系。就像是
{
onAbort:['topAbort'],onClick:['topClick']
}
-->
ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(dependency, topEventMapping[dependency], mountAt);
<!--这里的核心调用了trapBubbledEvent方法,也有一些情况是使用 trapCapturedEvent方法顾名思义,他们是在将事件注册在不同的事件流中(捕获事件流或者是冒泡事件流),下面我们以冒泡事件流为例说明。-->
}
ReactEventListener.trapBubbledEvent
// @param topLevelType:topAbort,topClick等
// @param handlerBaseName:abort,click等
// @param handle:一个document
trapBubbledEvent: function (topLevelType, handlerBaseName, handle) {
var element = handle;
if (!element) {
return null;
}
/*
注意:
listen方法主要是调用了addEventListener方法。而addEventListener方法接受三个参数。
// @param eventType:就是click,abort等。
// @param callback :就是事件触发的时候的回调函数。
// @param Boolean值: 表示在什么时候触发,冒泡阶段,还是捕获阶段默认是false,冒泡阶段。
而到了这里,我们没有拿到一个回调函数callback,你看给的三个参数是:topLevelType, handlerBaseName, handle
这里React给了一个函数 ReactEventListener.dispatchEvent.bind(null, topLevelType)来做回调函数。
这个回调函数,和我们绑定在页面上的回调函数不是一回事。
我们在页面上绑定的回调函数,最终是存储在了EventPluginHub的listenerBank中了。
*/
<!--核心还是调用了EventListener模块的listen方法-->
return EventListener.listen(element, handlerBaseName, ReactEventListener.dispatchEvent.bind(null, topLevelType));
},
EventListener.listen方法
<!-- 该方法就是调用了注册方法的API。抹平了不同浏览器的差异。在Target上注册了一个事件类型为eventType的事件
target是上边传过来的element,也就是一个document。
eventType:是一个事件类型,比如click,change等。
callback:我们之前说过,我们再组件内写的方法是没有注册在这的。
这里的callback是React提供的一个分发函数。ReactEventListener.dispatchEvent()。而我们在组件中写的方法最终是存储到了EventPluginHub中。
-->
listen: function (target, eventType, callback) {
if (target.addEventListener) {
target.addEventListener(eventType, callback, false);
return {
remove: function () {
target.removeEventListener(eventType, callback, false);
}
};
} else if (target.attachEvent) {
target.attachEvent('on' + eventType, callback);
return {
remove: function () {
target.detachEvent('on' + eventType, callback);
}
};
}
},