ReactBrowserEventEmitter

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;

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值