ReactBrowserEventEmitter模块区别EventPulginHub模块,后者用于缓存或获取组件实例的绑定函数,前者为节点绑定切实的回调函数,该回调函数执行过程中构建合成事件对象,获取组件实例的绑定回调并执行,若有state变更,则重绘组件。
'use strict'; var _assign = require('object-assign'); // 获取事件插件模块中各事件的依赖 var EventPluginRegistry = require('./EventPluginRegistry'); // 为ReactBrowserEventEmitter.ReactEventListener提供_handleTopLevel方法 // 该方法用于构建合成事件对象及执行该对象的绑定回调 var ReactEventEmitterMixin = require('./ReactEventEmitterMixin'); // scroll或resize事件事件发生时缓存滚动偏移量 var ViewportMetrics = require('./ViewportMetrics'); // 获取当前浏览器支持的animationend、animationiteration、animationstart、transitionend事件名 var getVendorPrefixedEventName = require('./getVendorPrefixedEventName'); // 判断是否支持某事件 var isEventSupported = require('./isEventSupported'); var hasEventPageXY; var alreadyListeningTo = {}; var isMonitoringScrollValue = false; var reactTopListenersCounter = 0; // 普通事件名到top事件名的映射 var topEventMapping = { topAbort: 'abort', topAnimationEnd: getVendorPrefixedEventName('animationend') || 'animationend', topAnimationIteration: getVendorPrefixedEventName('animationiteration') || 'animationiteration', topAnimationStart: getVendorPrefixedEventName('animationstart') || 'animationstart', topBlur: 'blur', topCanPlay: 'canplay', topCanPlayThrough: 'canplaythrough', topChange: 'change', topClick: 'click', topCompositionEnd: 'compositionend', topCompositionStart: 'compositionstart', topCompositionUpdate: 'compositionupdate', topContextMenu: 'contextmenu', topCopy: 'copy', topCut: 'cut', topDoubleClick: 'dblclick', topDrag: 'drag', topDragEnd: 'dragend', topDragEnter: 'dragenter', topDragExit: 'dragexit', topDragLeave: 'dragleave', topDragOver: 'dragover', topDragStart: 'dragstart', topDrop: 'drop', topDurationChange: 'durationchange', topEmptied: 'emptied', topEncrypted: 'encrypted', topEnded: 'ended', topError: 'error', topFocus: 'focus', topInput: 'input', topKeyDown: 'keydown', topKeyPress: 'keypress', topKeyUp: 'keyup', topLoadedData: 'loadeddata', topLoadedMetadata: 'loadedmetadata', topLoadStart: 'loadstart', topMouseDown: 'mousedown', topMouseMove: 'mousemove', topMouseOut: 'mouseout', topMouseOver: 'mouseover', topMouseUp: 'mouseup', topPaste: 'paste', topPause: 'pause', topPlay: 'play', topPlaying: 'playing', topProgress: 'progress', topRateChange: 'ratechange', topScroll: 'scroll', topSeeked: 'seeked', topSeeking: 'seeking', topSelectionChange: 'selectionchange', topStalled: 'stalled', topSuspend: 'suspend', topTextInput: 'textInput', topTimeUpdate: 'timeupdate', topTouchCancel: 'touchcancel', topTouchEnd: 'touchend', topTouchMove: 'touchmove', topTouchStart: 'touchstart', topTransitionEnd: getVendorPrefixedEventName('transitionend') || 'transitionend', topVolumeChange: 'volumechange', topWaiting: 'waiting', topWheel: 'wheel' }; // 缓存document绑定事件记录不与其他fragment下的document节点绑定事件数据起冲突 var topListenersIDKey = '_reactListenersID' + String(Math.random()).slice(2); // document节点mountAt已经绑定事件,不必再次绑定 function getListeningForDocument(mountAt) { // IE8 `mountAt` is a host object and doesn't have `hasOwnProperty` if (!Object.prototype.hasOwnProperty.call(mountAt, topListenersIDKey)) { mountAt[topListenersIDKey] = reactTopListenersCounter++; alreadyListeningTo[mountAt[topListenersIDKey]] = {}; } return alreadyListeningTo[mountAt[topListenersIDKey]]; } // react事件机制: // 通过ReactDefaultInjection模块注入ReactEventListener模块,设为ReactBrowserEventEmitter.ReactEventListener属性 // ReactBrowserEventEmitter为ReactEventListener属性提供_handleTopLevel方法,即ReactEventEmitterMixin模块的handleTopLevel // 该方法中调用EventPluginHub.extractEvents方法执行各事件插件如SimpleEventPlugin等的extractEvents方法,构建合成事件对象 // 再通过runEventQueueInBatch函数调用EventPluginHub的enqueueEvents、processEventQueue方法,执行合成事件对象的绑定回调函数 // EventPluginHub模块通过EventPluginRegistry调用已注册的事件插件的extractEvents方法,完成合成事件对象的构建 // 同时提供enqueueEvents、processEventQueue方法,执行合成事件对象的绑定回调函数,该过程调用EventPluginUtils模块中的方法 // 提供putListener将组件实例的事件回调存储在缓存中;deleteListener、deleteAllListeners删除缓存中绑定事件回调函数 // EventPluginRegistry用于加载并存储、或获取各类事件插件 // SimpleEventPlugin等事件插件提供extractEvents方法构建合成事件对象,并解决兼容性问题 // 合成事件对象构建过程调用EventPropagators的accumulateTwoPhaseDispatches等方法,为该对象添加绑定事件的回调函数及其相应组件 // EventPropagators模块为SyntheticEvent等合成对象实例添加_dispatchListeners、_dispatchInstances属性 // 即组件实例绑定的事件回调函数,该过程调用EventPluginUtils模块中的方法,用于获取父组件的冒泡、捕获事件 // EventPluginUtils模块为合成事件对象添加绑定的回调函数及关联的组件实例,或者获取绑定的回调函数并执行 // ReactBrowserEventEmitter各接口调用ReactEventListener模块方法,绑定事件,在浏览器事件触发后 // 构建合成事件对象,并调用合成事件对象的绑定回调函数;若此过程存在组件state变更,则重绘组件 // // react事件书写时分为两个过程 // 其一调用ReactBrowserEventEmitter的trapBubbledEvent等方法为节点或文档绑定事件 // 回调函数即ReactBrowserEventEmitter.ReactEventListener.dispatchEvent执行组件实例的绑定函数,state变更时予以重绘组件 // 其二调用EventPluginHub的putListener方法将组件实例的绑定回调函数存储到缓存中 // 事件触发后,ReactEventListener.dispatchEvent将把该回调函数赋予合成事件对象的_dispatchListener属性 // // +------------+ . // | DOM | . // +------------+ . // | . // v . // +------------+ . // | ReactEvent | . // | Listener | . // +------------+ . +-----------+ // | . +--------+|SimpleEvent| // | . | |Plugin | // +-----|------+ . v +-----------+ // | | | . +--------------+ +------------+ // | +-----------.--->|EventPluginHub| | Event | // | | . | | +-----------+ | Propagators| // | ReactEvent | . | | |TapEvent | |------------| // | Emitter | . | |<---+|Plugin | |other plugin| // | | . | | +-----------+ | utilities | // | +-----------.--->| | +------------+ // | | | . +--------------+ // +-----|------+ . ^ +-----------+ // | . | |Enter/Leave| // + . +-------+|Plugin | // +-------------+ . +-----------+ // | application | . // |-------------| . // | | . // | | . // +-------------+ . // 调用ReactBrowserEventEmitter.ReactEventListener方法以react方式监听节点事件 // 绑定ReactEventListener.dispatchEvent派发事件方法,回调中构建合成事件对象并执行合成事件对的象绑定回调 // listenTo方法为document节点绑定事件,使事件向fragment上层组件传播 var ReactBrowserEventEmitter = _assign({}, ReactEventEmitterMixin, { // 通过ReactDefaultInjection模块赋值为ReactEventListener模块 // 并向ReactEventListener模块添加_handleTopLevel=ReactEventEmitterMixin.handleTopLevel ReactEventListener: null, injection: { injectReactEventListener: function (ReactEventListener) { // ReactBrowserEventEmitter._handleTopLevel通过浅拷贝,赋值为ReactEventEmitterMixin.handleTopLevel ReactEventListener.setHandleTopLevel(ReactBrowserEventEmitter.handleTopLevel); // 通过ReactDefaultInjection模块赋值为ReactEventListener模块 ReactBrowserEventEmitter.ReactEventListener = ReactEventListener; } }, // 是否允许派发react事件,即在组件重绘后调用合成事件对象的绑定回调函数 setEnabled: function (enabled) { if (ReactBrowserEventEmitter.ReactEventListener) { ReactBrowserEventEmitter.ReactEventListener.setEnabled(enabled); } }, // 判断是否允许派发react事件 isEnabled: function () { return !!(ReactBrowserEventEmitter.ReactEventListener && ReactBrowserEventEmitter.ReactEventListener.isEnabled()); }, // firefox、safari、ie兼容性问题,或者不触发document节点的冒泡事件,或者可以冒泡到document上,却不能冒泡到window对象 // document节点由react机制创建,作为fragment下的节点,需要绑定事件,实现事件向fragment上层组件传播 // 参数registrationName为普通事件名如"click",contentDocumentHandle为document节点 listenTo: function (registrationName, contentDocumentHandle) { var mountAt = contentDocumentHandle; // 获取fragment下document节点已绑定事件的记录 var isListening = getListeningForDocument(mountAt); // 获取事件插件中各事件的依赖,如topClick等 var dependencies = EventPluginRegistry.registrationNameDependencies[registrationName]; for (var i = 0; i < dependencies.length; i++) { var dependency = dependencies[i]; if (!(isListening.hasOwnProperty(dependency) && isListening[dependency])) { if (dependency === 'topWheel') { if (isEventSupported('wheel')) { ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent('topWheel', 'wheel', mountAt); } else if (isEventSupported('mousewheel')) { ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent('topWheel', 'mousewheel', mountAt); } else { // Firefox needs to capture a different mouse scroll event. // @see http://www.quirksmode.org/dom/events/tests/scroll.html ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent('topWheel', 'DOMMouseScroll', mountAt); } } else if (dependency === 'topScroll') { if (isEventSupported('scroll', true)) { ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent('topScroll', 'scroll', mountAt); } else { ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent('topScroll', 'scroll', ReactBrowserEventEmitter.ReactEventListener.WINDOW_HANDLE); } } else if (dependency === 'topFocus' || dependency === 'topBlur') { if (isEventSupported('focus', true)) { ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent('topFocus', 'focus', mountAt); ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent('topBlur', 'blur', mountAt); } else if (isEventSupported('focusin')) { // IE has `focusin` and `focusout` events which bubble. // @see http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent('topFocus', 'focusin', mountAt); ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent('topBlur', 'focusout', mountAt); } // 保证只为document节点绑定一次focus、blur事件 isListening.topBlur = true; isListening.topFocus = true; } else if (topEventMapping.hasOwnProperty(dependency)) { ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(dependency, topEventMapping[dependency], mountAt); } isListening[dependency] = true; } } }, // 冒泡方式绑定事件,回调设为ReactEventListener.dispatchEvent,构建合成事件对象并执行该对象的回调 // 参数topLevelType如topClick,handlerBaseName如click,handle为绑定事件的节点 trapBubbledEvent: function (topLevelType, handlerBaseName, handle) { return ReactBrowserEventEmitter.ReactEventListener.trapBubbledEvent(topLevelType, handlerBaseName, handle); }, // 捕获方式绑定事件,回调设为ReactEventListener.dispatchEvent,构建合成事件对象并执行该对象的回调 trapCapturedEvent: function (topLevelType, handlerBaseName, handle) { return ReactBrowserEventEmitter.ReactEventListener.trapCapturedEvent(topLevelType, handlerBaseName, handle); }, // 判断浏览器是否支持event.pageX|pageY属性 supportsEventPageXY: function () { if (!document.createEvent) { return false; } var ev = document.createEvent('MouseEvent'); return ev != null && 'pageX' in ev; }, // 浏览器不支持event.pageX|pageY属性时,scroll或resize事件触发时将滚动偏移量赋值给ViewportMetrics ensureScrollValueMonitoring: function () { if (hasEventPageXY === undefined) { hasEventPageXY = ReactBrowserEventEmitter.supportsEventPageXY(); } if (!hasEventPageXY && !isMonitoringScrollValue) { var refresh = ViewportMetrics.refreshScrollValues; ReactBrowserEventEmitter.ReactEventListener.monitorScrollValue(refresh); isMonitoringScrollValue = true; } } }); module.exports = ReactBrowserEventEmitter;