SimpleEventPlugin等事件插件

SimpleEventPlugin、EnterLeaveEventPlugin、ChangeEventPlugin、SelectEventPlugin、BeforeInputEventPlugin事件插件调用SyntheticEvent等构造函数创建合成事件对象(该过程在事件触发时执行,因此对不能绑定事件回调函数的兼容性问题,可以在事件插件中人工绑定react型事件的回调函数;react型事件作为原生事件触发时的回调函数),创建顺序由EventPluginRegistry模块注入的DefaultEventPluginOrder约定。

事件插件不能存在同名事件,且必须包含extractEvents方法;事件名描述对象eventTypes的dependencies属性约定浏览器下触发的基础事件,该事件可共用,如SimpleEventPlugin事件插件模块的事件mouseOut依赖topMouseOut事件,EnterLeaveEventPlugin事件插件的mouseEnter插件也依赖topMouseOut事件。

 

SimpleEventPlugin.js

'use strict';

var _prodInvariant = require('./reactProdInvariant');

// 绑定、解绑事件
var EventListener = require('fbjs/lib/EventListener');

// 为合成事件对象添加_dispatchListeners、_dispatchInstances属性记录绑定回调及相应组件实例
var EventPropagators = require('./EventPropagators');

var ReactDOMComponentTree = require('./ReactDOMComponentTree');

// 合成事件对象的构造函数
var SyntheticAnimationEvent = require('./SyntheticAnimationEvent');
var SyntheticClipboardEvent = require('./SyntheticClipboardEvent');
var SyntheticEvent = require('./SyntheticEvent');
var SyntheticFocusEvent = require('./SyntheticFocusEvent');
var SyntheticKeyboardEvent = require('./SyntheticKeyboardEvent');
var SyntheticMouseEvent = require('./SyntheticMouseEvent');
var SyntheticDragEvent = require('./SyntheticDragEvent');
var SyntheticTouchEvent = require('./SyntheticTouchEvent');
var SyntheticTransitionEvent = require('./SyntheticTransitionEvent');
var SyntheticUIEvent = require('./SyntheticUIEvent');
var SyntheticWheelEvent = require('./SyntheticWheelEvent');

var emptyFunction = require('fbjs/lib/emptyFunction');
var getEventCharCode = require('./getEventCharCode');
var invariant = require('fbjs/lib/invariant');

/**
 * 针对不同事件构建事件类型
 * 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;
});

var onClickListeners = {};

function getDictionaryKey(inst) {
  // Prevents V8 performance issue:
  // https://github.com/facebook/react/pull/7232
  return '.' + inst._rootNodeID;
}

function isInteractive(tag) {
  return tag === 'button' || tag === 'input' || tag === 'select' || tag === 'textarea';
}

var SimpleEventPlugin = {
  // SimpleEvent类所有事件类型,其属性值将作为合成事件对象的dispatchConfig属性
  // {click:{phasedRegistrationNames:{bubbled:"onClick",captured:"onClickCapture"},dependencies:["topClick"]}}格式
  eventTypes: eventTypes,

  // 提取合成事件对象event,该对象在原生事件对象的基础上添加dispatchConfig记录相应的事件类型
  // 同时event.dispatchConfig属性也用于获取组件实例的相应绑定回调函数
  // event对象添加_dispatchListeners、_dispatchInstances以存储绑定回调函数及对应的组件实例
  extractEvents: function (topLevelType, targetInst, nativeEvent, nativeEventTarget) {
    var dispatchConfig = topLevelEventsToDispatchConfig[topLevelType];
    if (!dispatchConfig) {
      return null;
    }

    // 获取事件对应的创建react合成事件对象的构造函数
    var EventConstructor;
    switch (topLevelType) {
      case 'topAbort':
      case 'topCanPlay':
      case 'topCanPlayThrough':
      case 'topDurationChange':
      case 'topEmptied':
      case 'topEncrypted':
      case 'topEnded':
      case 'topError':
      case 'topInput':
      case 'topInvalid':
      case 'topLoad':
      case 'topLoadedData':
      case 'topLoadedMetadata':
      case 'topLoadStart':
      case 'topPause':
      case 'topPlay':
      case 'topPlaying':
      case 'topProgress':
      case 'topRateChange':
      case 'topReset':
      case 'topSeeked':
      case 'topSeeking':
      case 'topStalled':
      case 'topSubmit':
      case 'topSuspend':
      case 'topTimeUpdate':
      case 'topVolumeChange':
      case 'topWaiting':
        // HTML Events
        // @see http://www.w3.org/TR/html5/index.html#events-0
        EventConstructor = SyntheticEvent;
        break;
      case 'topKeyPress':
        // Firefox creates a keypress event for function keys too. This removes
        // the unwanted keypress events. Enter is however both printable and
        // non-printable. One would expect Tab to be as well (but it isn't).
        if (getEventCharCode(nativeEvent) === 0) {
          return null;
        }
      /* falls through */
      case 'topKeyDown':
      case 'topKeyUp':
        EventConstructor = SyntheticKeyboardEvent;
        break;
      case 'topBlur':
      case 'topFocus':
        EventConstructor = SyntheticFocusEvent;
        break;
      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':
        EventConstructor = SyntheticMouseEvent;
        break;
      case 'topDrag':
      case 'topDragEnd':
      case 'topDragEnter':
      case 'topDragExit':
      case 'topDragLeave':
      case 'topDragOver':
      case 'topDragStart':
      case 'topDrop':
        EventConstructor = SyntheticDragEvent;
        break;
      case 'topTouchCancel':
      case 'topTouchEnd':
      case 'topTouchMove':
      case 'topTouchStart':
        EventConstructor = SyntheticTouchEvent;
        break;
      case 'topAnimationEnd':
      case 'topAnimationIteration':
      case 'topAnimationStart':
        EventConstructor = SyntheticAnimationEvent;
        break;
      case 'topTransitionEnd':
        EventConstructor = SyntheticTransitionEvent;
        break;
      case 'topScroll':
        EventConstructor = SyntheticUIEvent;
        break;
      case 'topWheel':
        EventConstructor = SyntheticWheelEvent;
        break;
      case 'topCopy':
      case 'topCut':
      case 'topPaste':
        EventConstructor = SyntheticClipboardEvent;
        break;
    }
    !EventConstructor ? 
      process.env.NODE_ENV !== 'production' ? 
        invariant(false, 'SimpleEventPlugin: Unhandled event type, `%s`.', topLevelType) 
        : _prodInvariant('86', topLevelType) 
      : void 0;

    // 获取合成事件对象
    var event = EventConstructor.getPooled(dispatchConfig, targetInst, nativeEvent, nativeEventTarget);
    
    // 为event对象添加触发事件实例及其直系父组件的绑定回调函数,含捕获和冒泡事件
    EventPropagators.accumulateTwoPhaseDispatches(event);
    return event;
  },

  didPutListener: function (inst, registrationName, listener) {
    // Mobile Safari does not fire properly bubble click events on
    // non-interactive elements, which means delegated click listeners do not
    // fire. The workaround for this bug involves attaching an empty click
    // listener on the target node.
    // http://www.quirksmode.org/blog/archives/2010/09/click_event_del.html
    if (registrationName === 'onClick' && !isInteractive(inst._tag)) {
      var key = getDictionaryKey(inst);
      var node = ReactDOMComponentTree.getNodeFromInstance(inst);
      if (!onClickListeners[key]) {
        onClickListeners[key] = EventListener.listen(node, 'click', emptyFunction);
      }
    }
  },

  willDeleteListener: function (inst, registrationName) {
    if (registrationName === 'onClick' && !isInteractive(inst._tag)) {
      var key = getDictionaryKey(inst);
      onClickListeners[key].remove();
      delete onClickListeners[key];
    }
  }

};

module.exports = SimpleEventPlugin;

 

EnterLeaveEventPlugin.js

'use strict';

// 为合成事件对象添加_dispatchListeners、_dispatchInstances属性记录绑定回调及相应组件实例
var EventPropagators = require('./EventPropagators');

// 由节点查找组件实例,或由组件实例查找节点
var ReactDOMComponentTree = require('./ReactDOMComponentTree');

// 构建SyntheticMouseEvent类合成事件对象的基类
var SyntheticMouseEvent = require('./SyntheticMouseEvent');

var eventTypes = {
  mouseEnter: {
    registrationName: 'onMouseEnter',
    dependencies: ['topMouseOut', 'topMouseOver']
  },
  mouseLeave: {
    registrationName: 'onMouseLeave',
    dependencies: ['topMouseOut', 'topMouseOver']
  }
};

var EnterLeaveEventPlugin = {

  eventTypes: eventTypes,

  // 获取移开、移入合成事件对象
  // 只接受'topMouseOut'或'topMouseOver'事件;'topMouseOver'事件还要求移出节点为空,移出节点将设为window对象
  // 该事件插件模块在SimpleEventPlguin插件后调用,即'topMouseOut'在SimpleEventPlguin模块中构建SyntheticMouseEvent类合成事件的同时
  // 在当前模块中构建移入、移出合成事件对象
  extractEvents: function (topLevelType, targetInst, nativeEvent, nativeEventTarget) {
    if (topLevelType === 'topMouseOver' && (nativeEvent.relatedTarget || nativeEvent.fromElement)) {
      return null;
    }

    // 只接受'topMouseOut'或'topMouseOver'事件
    if (topLevelType !== 'topMouseOut' && topLevelType !== 'topMouseOver') {
      return null;
    }

    // 获取相关的window对象
    var win;
    if (nativeEventTarget.window === nativeEventTarget) {
      win = nativeEventTarget;
    } else {
      var doc = nativeEventTarget.ownerDocument;
      if (doc) {
        win = doc.defaultView || doc.parentWindow;
      } else {
        win = window;
      }
    }

    // 获取鼠标移开、移入的节点fromNode、toNode,不存在时赋值为window对象
    var from;
    var to;
    if (topLevelType === 'topMouseOut') {
      from = targetInst;
      var related = nativeEvent.relatedTarget || nativeEvent.toElement;
      to = related ? ReactDOMComponentTree.getClosestInstanceFromNode(related) : null;
    } else {
      from = null;
      to = targetInst;
    }

    if (from === to) {
      return null;
    }

    var fromNode = from == null ? win : ReactDOMComponentTree.getNodeFromInstance(from);
    var toNode = to == null ? win : ReactDOMComponentTree.getNodeFromInstance(to);

    // 移开、移入合成事件对象的relatedTarget属性赋值为对应的移入、移出节点
    var leave = SyntheticMouseEvent.getPooled(eventTypes.mouseLeave, from, nativeEvent, nativeEventTarget);
    leave.type = 'mouseleave';
    leave.target = fromNode;
    leave.relatedTarget = toNode;

    var enter = SyntheticMouseEvent.getPooled(eventTypes.mouseEnter, to, nativeEvent, nativeEventTarget);
    enter.type = 'mouseenter';
    enter.target = toNode;
    enter.relatedTarget = fromNode;

    // 为移开、移入合成事件对象添加父组件实例的绑定回调函数
    EventPropagators.accumulateEnterLeaveDispatches(leave, enter, from, to);

    // 返回移开、移入合成事件对象,在EventPluginHub模块的extractEvents方法中通过调用accumulateInto函数添加到events对象队列中
    return [leave, enter];
  }

};

module.exports = EnterLeaveEventPlugin;

 

ChangeEventPlugin.js

'use strict';

// 执行合成事件对象的绑定回调函数
var EventPluginHub = require('./EventPluginHub');

// 向合成事件对象添加绑定回调函数
var EventPropagators = require('./EventPropagators');
var ExecutionEnvironment = require('fbjs/lib/ExecutionEnvironment');

// 通过组件实例获取节点,或者通过节点获取组件实例
var ReactDOMComponentTree = require('./ReactDOMComponentTree');

// ReactUpdates.batchedUpdates执行回调,存在脏组件时,重绘组件
var ReactUpdates = require('./ReactUpdates');

// 构建合成事件对象的基类
var SyntheticEvent = require('./SyntheticEvent');

var getEventTarget = require('./getEventTarget');

// 判断浏览器环境是否支持某事件
var isEventSupported = require('./isEventSupported');

// 判断元素是都textarea或input元素
var isTextInputElement = require('./isTextInputElement');

var eventTypes = {
  change: {
    phasedRegistrationNames: {
      bubbled: 'onChange',
      captured: 'onChangeCapture'
    },
    dependencies: ['topBlur', 'topChange', 'topClick', 'topFocus', 'topInput', 'topKeyDown', 'topKeyUp', 'topSelectionChange']
  }
};

// 针对ie的兼容性问题,存储改变中的表单项元素input和textarea相关值
var activeElement = null;
var activeElementInst = null;
var activeElementValue = null;
var activeElementValueProp = null;

// 判断节点是否select元素或input(type=file)元素
function shouldUseChangeEvent(elem) {
  var nodeName = elem.nodeName && elem.nodeName.toLowerCase();
  return nodeName === 'select' || nodeName === 'input' && elem.type === 'file';
}

// 判断是否非Ie8以下浏览器、且支持change事件
var doesChangeEventBubble = false;
if (ExecutionEnvironment.canUseDOM) {
  doesChangeEventBubble = isEventSupported('change') && (!document.documentMode || document.documentMode > 8);
}

// 针对兼容性问题,使用react机制在浏览器事件触发时,重绘组件完成后,触发react合成事件对象的绑定回调函数
function manualDispatchChangeEvent(nativeEvent) {
  var event = SyntheticEvent.getPooled(eventTypes.change, activeElementInst, nativeEvent, getEventTarget(nativeEvent));
  EventPropagators.accumulateTwoPhaseDispatches(event);

  ReactUpdates.batchedUpdates(runEventInBatch, event);
}

// react机制,组件完成时,触发合成事件的回调
function runEventInBatch(event) {
  EventPluginHub.enqueueEvents(event);
  EventPluginHub.processEventQueue(false);
}

// ie8下绑定'onchange'事件
function startWatchingForChangeEventIE8(target, targetInst) {
  activeElement = target;
  activeElementInst = targetInst;
  activeElement.attachEvent('onchange', manualDispatchChangeEvent);
}

// ie8下解绑'onchange'事件
function stopWatchingForChangeEventIE8() {
  if (!activeElement) {
    return;
  }
  activeElement.detachEvent('onchange', manualDispatchChangeEvent);
  activeElement = null;
  activeElementInst = null;
}

// 筛选topChange事件
function getTargetInstForChangeEvent(topLevelType, targetInst) {
  if (topLevelType === 'topChange') {
    return targetInst;
  }
}

// ie8以下浏览器元素获得焦点时调用manualDispatchChangeEvent绑定react事件,失去焦点时解绑
function handleEventsForChangeEventIE8(topLevelType, target, targetInst) {
  if (topLevelType === 'topFocus') {
    stopWatchingForChangeEventIE8();
    startWatchingForChangeEventIE8(target, targetInst);
  } else if (topLevelType === 'topBlur') {
    stopWatchingForChangeEventIE8();
  }
}

// 判断是否非Ie11以下浏览器、且支持input事件
var isInputEventSupported = false;
if (ExecutionEnvironment.canUseDOM) {
  isInputEventSupported = isEventSupported('input') && (!document.documentMode || document.documentMode > 11);
}

// 'onpropertychange'、'propertychange'事件表单项值改变时,同时影响activeElementValue的值
var newValueProp = {
  get: function () {
    return activeElementValueProp.get.call(this);
  },
  set: function (val) {
    activeElementValue = '' + val;
    activeElementValueProp.set.call(this, val);
  }
};

// ie10以下解绑'onpropertychange'、'propertychange'事件,调用manualDispatchChangeEvent函数派发react事件
function startWatchingForValueChange(target, targetInst) {
  activeElement = target;
  activeElementInst = targetInst;
  activeElementValue = target.value;
  activeElementValueProp = Object.getOwnPropertyDescriptor(target.constructor.prototype, 'value');

  Object.defineProperty(activeElement, 'value', newValueProp);
  if (activeElement.attachEvent) {
    activeElement.attachEvent('onpropertychange', handlePropertyChange);
  } else {
    activeElement.addEventListener('propertychange', handlePropertyChange, false);
  }
}

// 解绑'onpropertychange'、'propertychange'事件
function stopWatchingForValueChange() {
  if (!activeElement) {
    return;
  }

  delete activeElement.value;

  if (activeElement.detachEvent) {
    activeElement.detachEvent('onpropertychange', handlePropertyChange);
  } else {
    activeElement.removeEventListener('propertychange', handlePropertyChange, false);
  }

  activeElement = null;
  activeElementInst = null;
  activeElementValue = null;
  activeElementValueProp = null;
}

// ie10以下当表单元素值改变时调用manualDispatchChangeEvent派发react事件
function handlePropertyChange(nativeEvent) {
  if (nativeEvent.propertyName !== 'value') {
    return;
  }
  var value = nativeEvent.srcElement.value;
  if (value === activeElementValue) {
    return;
  }
  activeElementValue = value;

  manualDispatchChangeEvent(nativeEvent);
}

// 筛选topInput事件
function getTargetInstForInputEvent(topLevelType, targetInst) {
  if (topLevelType === 'topInput') {
    return targetInst;
  }
}

// ie10以下浏览器元素获得焦点时调用manualDispatchChangeEvent绑定react事件,失去焦点时解绑
function handleEventsForInputEventIE(topLevelType, target, targetInst) {
  if (topLevelType === 'topFocus') {
    stopWatchingForValueChange();
    startWatchingForValueChange(target, targetInst);
  } else if (topLevelType === 'topBlur') {
    stopWatchingForValueChange();
  }
}

// IE8、IE9筛选'topSelectionChange'、'topKeyUp'、'topKeyDown'事件,activeElementValue更迭为聚焦节点的值
// 返回activeElementInst
// activeElement值在startWatchingForValueChange函数中改变,即输入框获得焦点时
// getTargetInstForInputEventIE函数执行在输入框失去焦点时
function getTargetInstForInputEventIE(topLevelType, targetInst) {
  if (topLevelType === 'topSelectionChange' || topLevelType === 'topKeyUp' || topLevelType === 'topKeyDown') {
    if (activeElement && activeElement.value !== activeElementValue) {
      activeElementValue = activeElement.value;
      return activeElementInst;
    }
  }
}

// 判断是否radio、checkbox节点
function shouldUseClickEvent(elem) {
  return elem.nodeName && elem.nodeName.toLowerCase() === 'input' && (elem.type === 'checkbox' || elem.type === 'radio');
}

// 筛选topClick事件
function getTargetInstForClickEvent(topLevelType, targetInst) {
  if (topLevelType === 'topClick') {
    return targetInst;
  }
}

// 构建表单项change事件合成对象
var ChangeEventPlugin = {

  eventTypes: eventTypes,

  extractEvents: function (topLevelType, targetInst, nativeEvent, nativeEventTarget) {
    var targetNode = targetInst ? ReactDOMComponentTree.getNodeFromInstance(targetInst) : window;

    var getTargetInstFunc, handleEventFunc;

    // shouldUseChangeEvent判断节点targetNode是否select元素或input(type=file)元素
    if (shouldUseChangeEvent(targetNode)) {
      // doesChangeEventBubble判断是否非Ie8以下浏览器、且支持change事件
      if (doesChangeEventBubble) {
        // getTargetInstForChangeEvent函数用来筛选topChange事件
        getTargetInstFunc = getTargetInstForChangeEvent;
      } else {
        // ie8以下浏览器元素获得焦点时调用manualDispatchChangeEvent绑定react事件,失去焦点时解绑
        handleEventFunc = handleEventsForChangeEventIE8;
      }

    // isTextInputElement判断节点targetNode是都textarea或input元素
    } else if (isTextInputElement(targetNode)) {
      // isInputEventSupported判断是否非Ie11以下浏览器、且支持input事件
      if (isInputEventSupported) {
        // getTargetInstForInputEvent函数用来筛选topInput事件
        getTargetInstFunc = getTargetInstForInputEvent;
      } else {
        // IE8、IE9筛选'topSelectionChange'、'topKeyUp'、'topKeyDown'事件
        // activeElementValue更迭为聚焦节点的值,返回activeElementInst
        getTargetInstFunc = getTargetInstForInputEventIE;
        // ie10以下浏览器元素获得焦点时调用manualDispatchChangeEvent绑定react事件,失去焦点时解绑
        handleEventFunc = handleEventsForInputEventIE;
      }

    // shouldUseClickEvent判断是否radio、checkbox节点
    } else if (shouldUseClickEvent(targetNode)) {
      // getTargetInstForClickEvent函数用于筛选topClick事件
      getTargetInstFunc = getTargetInstForClickEvent;
    }

    // 构建表单项change合成事件对象,event.type属性改为change,并添加父组件的绑定捕获、冒泡事件回调
    if (getTargetInstFunc) {
      var inst = getTargetInstFunc(topLevelType, targetInst);
      if (inst) {
        var event = SyntheticEvent.getPooled(eventTypes.change, inst, nativeEvent, nativeEventTarget);
        event.type = 'change';
        EventPropagators.accumulateTwoPhaseDispatches(event);
        return event;
      }
    }

    // 针对兼容性问题手动向浏览器绑定事件,事件发生时调用manualDispatchChangeEvent函数触发react事件
    // 再执行合成事件对象的回调函数
    if (handleEventFunc) {
      handleEventFunc(topLevelType, targetNode, targetInst);
    }
  }

};

module.exports = ChangeEventPlugin;

 

SelectEventPlugin.js

'use strict';

// 向合成事件对象添加绑定回调函数
var EventPropagators = require('./EventPropagators');

var ExecutionEnvironment = require('fbjs/lib/ExecutionEnvironment');

// 通过组件实例获取节点,或者通过节点获取组件实例
var ReactDOMComponentTree = require('./ReactDOMComponentTree');

// eactInputSelection.hasSelectionCapabilities判断当前浏览器凭条是否有文本选中能力
var ReactInputSelection = require('./ReactInputSelection');

// 构建合成事件对象的基类
var SyntheticEvent = require('./SyntheticEvent');

// 获取当前获得焦点的节点
var getActiveElement = require('fbjs/lib/getActiveElement');

// 判断元素是都textarea或input元素
var isTextInputElement = require('./isTextInputElement');

// 浅比较
var shallowEqual = require('fbjs/lib/shallowEqual');

// ie10以下不支持选中文本改变事件
var skipSelectionChangeEvent = ExecutionEnvironment.canUseDOM && 'documentMode' in document && document.documentMode <= 11;

var eventTypes = {
  select: {
    phasedRegistrationNames: {
      bubbled: 'onSelect',
      captured: 'onSelectCapture'
    },
    dependencies: ['topBlur', 'topContextMenu', 'topFocus', 'topKeyDown', 'topKeyUp', 'topMouseDown', 'topMouseUp', 'topSelectionChange']
  }
};

var activeElement = null;
var activeElementInst = null;
var lastSelection = null;
var mouseDown = false;

// Track whether a listener exists for this plugin. If none exist, we do
// not extract events. See #3639.
var hasListener = false;

// 生成constructSelectEvent函数中的currentSelection、lastSelection,用于判断选中文本是否发生改变
function getSelection(node) {
  if ('selectionStart' in node && ReactInputSelection.hasSelectionCapabilities(node)) {
    return {
      start: node.selectionStart,
      end: node.selectionEnd
    };
  } else if (window.getSelection) {
    var selection = window.getSelection();
    return {
      anchorNode: selection.anchorNode,
      anchorOffset: selection.anchorOffset,
      focusNode: selection.focusNode,
      focusOffset: selection.focusOffset
    };
  } else if (document.selection) {
    var range = document.selection.createRange();
    return {
      parentElement: range.parentElement(),
      text: range.text,
      top: range.boundingTop,
      left: range.boundingLeft
    };
  }
}

// 'topContextMenu'、'topMouseUp'、'topSelectionChange'、'topKeyDown'、'topKeyUp'事件发生时构建文本选中合成还是件对象
function constructSelectEvent(nativeEvent, nativeEventTarget) {
  // 'topMouseDown'事件发生或当前获得焦点的节点不是绑定事件的节点时,不予构建文本选中合成事件对象
  if (mouseDown || activeElement == null || activeElement !== getActiveElement()) {
    return null;
  }

  var currentSelection = getSelection(activeElement);
  if (!lastSelection || !shallowEqual(lastSelection, currentSelection)) {
    lastSelection = currentSelection;

    var syntheticEvent = SyntheticEvent.getPooled(eventTypes.select, activeElementInst, nativeEvent, nativeEventTarget);

    syntheticEvent.type = 'select';
    syntheticEvent.target = activeElement;

    EventPropagators.accumulateTwoPhaseDispatches(syntheticEvent);

    return syntheticEvent;
  }

  return null;
}

// 用于构建input或textarea节点选中文本时的合成事件对象
var SelectEventPlugin = {

  eventTypes: eventTypes,

  extractEvents: function (topLevelType, targetInst, nativeEvent, nativeEventTarget) {
    // hasListener值在didPutListener方法中改变,即浏览器事件发生时改变
    // extractEvents方法执行在浏览器事件发生后引起组价重绘时,hasListener值已被改变
    if (!hasListener) {
      return null;
    }

    var targetNode = targetInst ? ReactDOMComponentTree.getNodeFromInstance(targetInst) : window;

    switch (topLevelType) {
      // Track the input node that has focus.
      case 'topFocus':
        if (isTextInputElement(targetNode) || targetNode.contentEditable === 'true') {
          activeElement = targetNode;
          activeElementInst = targetInst;
          lastSelection = null;
        }
        break;
      case 'topBlur':
        activeElement = null;
        activeElementInst = null;
        lastSelection = null;
        break;

      case 'topMouseDown':
        mouseDown = true;
        break;
      case 'topContextMenu':
      case 'topMouseUp':
        mouseDown = false;
        return constructSelectEvent(nativeEvent, nativeEventTarget);

      case 'topSelectionChange':
        if (skipSelectionChangeEvent) {
          break;
        }

      case 'topKeyDown':
      case 'topKeyUp':
        return constructSelectEvent(nativeEvent, nativeEventTarget);
    }

    return null;
  },

  didPutListener: function (inst, registrationName, listener) {
    if (registrationName === 'onSelect') {
      hasListener = true;
    }
  }
};

module.exports = SelectEventPlugin;

 

BeforeInputEventPlugin.js

'use strict';

// 向合成事件对象添加绑定回调函数
var EventPropagators = require('./EventPropagators');
var ExecutionEnvironment = require('fbjs/lib/ExecutionEnvironment');

// 用于获取中文输入法时插入的文本
var FallbackCompositionState = require('./FallbackCompositionState');

// 合成事件对象的构造函数
var SyntheticCompositionEvent = require('./SyntheticCompositionEvent');
var SyntheticInputEvent = require('./SyntheticInputEvent');

var END_KEYCODES = [9, 13, 27, 32]; // Tab, Return, Esc, Space
var START_KEYCODE = 229;// 中文或日文输入法等时按键keycode改为229

// 用以判断ie浏览器的版本
var documentMode = null;
if (ExecutionEnvironment.canUseDOM && 'documentMode' in document) {
  documentMode = document.documentMode;
}

// 是否Opera <= 12
function isPresto() {
  var opera = window.opera;
  return typeof opera === 'object' && typeof opera.version === 'function' && parseInt(opera.version(), 10) <= 12;
}

// 判断浏览器是否支持'CompositionEvent'事件
var canUseCompositionEvent = ExecutionEnvironment.canUseDOM && 'CompositionEvent' in window;

// 浏览器支持TextEvent事件,同时浏览器不是ie或者opera12以下版本
var canUseTextInputEvent = ExecutionEnvironment.canUseDOM && 'TextEvent' in window && !documentMode && !isPresto();

// 判断浏览器是否不支持'CompositionEvent'事件或者不是ie9-11版本('CompositionEvent'事件由兼容性问题)
var useFallbackCompositionData = ExecutionEnvironment.canUseDOM && (!canUseCompositionEvent || documentMode && documentMode > 8 && documentMode <= 11);

var SPACEBAR_CODE = 32;
// String.fromCharCode将unicode编码转换为字符,32返回" "
var SPACEBAR_CHAR = String.fromCharCode(SPACEBAR_CODE);

var eventTypes = {
  beforeInput: {
    phasedRegistrationNames: {
      bubbled: 'onBeforeInput',
      captured: 'onBeforeInputCapture'
    },
    dependencies: ['topCompositionEnd', 'topKeyPress', 'topTextInput', 'topPaste']
  },
  compositionEnd: {
    phasedRegistrationNames: {
      bubbled: 'onCompositionEnd',
      captured: 'onCompositionEndCapture'
    },
    dependencies: ['topBlur', 'topCompositionEnd', 'topKeyDown', 'topKeyPress', 'topKeyUp', 'topMouseDown']
  },
  compositionStart: {
    phasedRegistrationNames: {
      bubbled: 'onCompositionStart',
      captured: 'onCompositionStartCapture'
    },
    dependencies: ['topBlur', 'topCompositionStart', 'topKeyDown', 'topKeyPress', 'topKeyUp', 'topMouseDown']
  },
  compositionUpdate: {
    phasedRegistrationNames: {
      bubbled: 'onCompositionUpdate',
      captured: 'onCompositionUpdateCapture'
    },
    dependencies: ['topBlur', 'topCompositionUpdate', 'topKeyDown', 'topKeyPress', 'topKeyUp', 'topMouseDown']
  }
};

// Track whether we've ever handled a keypress on the space key.
var hasSpaceKeypress = false;

// 按键是否命令,如ctrl+c等,同时按下ctrl+alt不是命令
function isKeypressCommand(nativeEvent) {
  return (nativeEvent.ctrlKey || nativeEvent.altKey || nativeEvent.metaKey) &&
  !(nativeEvent.ctrlKey && nativeEvent.altKey);
}

// 筛选'topCompositionStart'、'topCompositionEnd'、'topCompositionUpdate'事件
function getCompositionEventType(topLevelType) {
  switch (topLevelType) {
    case 'topCompositionStart':
      return eventTypes.compositionStart;
    case 'topCompositionEnd':
      return eventTypes.compositionEnd;
    case 'topCompositionUpdate':
      return eventTypes.compositionUpdate;
  }
}

// 筛选'topKeyDown',同时通过keyCode判断是否为中文等特殊输入法(即'CompositionEvent'事件触发状态)
function isFallbackCompositionStart(topLevelType, nativeEvent) {
  return topLevelType === 'topKeyDown' && nativeEvent.keyCode === START_KEYCODE;
}

// 通过键盘松开等状态判断是否'CompositionEventEnd'事件
function isFallbackCompositionEnd(topLevelType, nativeEvent) {
  switch (topLevelType) {
    case 'topKeyUp':
      // Tab, Return, Esc, Space按键设为'CompositionEvent'事件中
      return END_KEYCODES.indexOf(nativeEvent.keyCode) !== -1;
    case 'topKeyDown':
      // 非中文等特殊输入法时也设为'CompositionEvent'事件中
      return nativeEvent.keyCode !== START_KEYCODE;
    case 'topKeyPress':// 键盘按下并松开,
    case 'topMouseDown':
    case 'topBlur':
      // Events are not possible without cancelling IME.
      return true;
    default:
      return false;
  }
}

// Google获取nativeEvent.detail.data属性
function getDataFromCustomEvent(nativeEvent) {
  var detail = nativeEvent.detail;
  if (typeof detail === 'object' && 'data' in detail) {
    return detail.data;
  }
  return null;
}

var currentComposition = null;

// 构建中文等特殊输入法时的CompositionEvent合成事件对象
function extractCompositionEvent(topLevelType, targetInst, nativeEvent, nativeEventTarget) {
  var eventType;
  var fallbackData;

  // canUseCompositionEvent判断浏览器是否支持'CompositionEvent'事件
  if (canUseCompositionEvent) {
    // getCompositionEventType筛选'topCompositionStart'、'topCompositionEnd'、'topCompositionUpdate'事件
    eventType = getCompositionEventType(topLevelType);

  // currentComposition当'CompositionEvent'事件开始时赋予真值FallbackCompositionState实例
  } else if (!currentComposition) {
    // isFallbackCompositionStart筛选'topKeyDown',同时通过keyCode判断是否为中文等特殊输入法(即'CompositionEvent'事件触发状态)
    if (isFallbackCompositionStart(topLevelType, nativeEvent)) {
      eventType = eventTypes.compositionStart;
    }

  // 通过键盘松开等状态判断是否'CompositionEventEnd'事件
  } else if (isFallbackCompositionEnd(topLevelType, nativeEvent)) {
    eventType = eventTypes.compositionEnd;
  }

  if (!eventType) {
    return null;
  }

  // useFallbackCompositionData判断浏览器是否不支持'CompositionEvent'事件或者不是ie9-11版本('CompositionEvent'事件由兼容性问题)
  if (useFallbackCompositionData) {
    if (!currentComposition && eventType === eventTypes.compositionStart) {
      // currentComposition赋值为FallbackCompositionState实例
      currentComposition = FallbackCompositionState.getPooled(nativeEventTarget);
    } else if (eventType === eventTypes.compositionEnd) {
      if (currentComposition) {
        // FallbackCompositionState实例的getData方法用于光标回退后的文本插入值???
        fallbackData = currentComposition.getData();
      }
    }
  }

  var event = SyntheticCompositionEvent.getPooled(eventType, targetInst, nativeEvent, nativeEventTarget);

  if (fallbackData) {
    event.data = fallbackData;
  } else {
    // getDataFromCustomEvent函数在chrome浏览器下获取nativeEvent.detail.data属性
    var customData = getDataFromCustomEvent(nativeEvent);
    if (customData !== null) {
      event.data = customData;
    }
  }

  EventPropagators.accumulateTwoPhaseDispatches(event);
  return event;
}

// 获取'topCompositionEnd'事件的输入文本、'topKeyPress'事件的空格输入、'topTextInput'的其他字符输入
function getNativeBeforeInputChars(topLevelType, nativeEvent) {
  switch (topLevelType) {
    case 'topCompositionEnd':
      // getDataFromCustomEvent函数在chrome浏览器下获取nativeEvent.detail.data属性
      return getDataFromCustomEvent(nativeEvent);
    case 'topKeyPress':
      // 针对'topTextInput'事件空格按键在chrome的bug问题
      var which = nativeEvent.which;// 是否空格键
      if (which !== SPACEBAR_CODE) {
        return null;
      }

      hasSpaceKeypress = true;
      return SPACEBAR_CHAR;
    case 'topTextInput':
      // 除空格外获取'topTextInput'事件的输入文本
      var chars = nativeEvent.data;

      if (chars === SPACEBAR_CHAR && hasSpaceKeypress) {
        return null;
      }

      return chars;
    default:
      return null;
  }
}

// 浏览器不支持TextInput事件,通过CompositionEvent事件获取输入文本;并排除粘贴命令等
function getFallbackBeforeInputChars(topLevelType, nativeEvent) {
  // 不支持CompositionEvent事件的环境,使用FallbackCompositionState实例获取输入字符
  if (currentComposition) {
    if (topLevelType === 'topCompositionEnd' || !canUseCompositionEvent && isFallbackCompositionEnd(topLevelType, nativeEvent)) {
      var chars = currentComposition.getData();
      FallbackCompositionState.release(currentComposition);
      currentComposition = null;
      return chars;
    }
    return null;
  }

  switch (topLevelType) {
    case 'topPaste':
      // 粘贴事件不获取文本
      return null;
    case 'topKeyPress':
      // 针对Firefox键盘命令也能触发KeyPress事件
      if (nativeEvent.which && !isKeypressCommand(nativeEvent)) {
        return String.fromCharCode(nativeEvent.which);
      }
      return null;
    case 'topCompositionEnd':
      // 支持CompositionEvent事件的环境,使用nativeEvent.data获取输入字符
      return useFallbackCompositionData ? null : nativeEvent.data;
    default:
      return null;
  }
}

// 构建向输入框插值操作的beforeInput合成事件对象
function extractBeforeInputEvent(topLevelType, targetInst, nativeEvent, nativeEventTarget) {
  var chars;

  // canUseTextInputEvent浏览器支持TextEvent事件,同时浏览器不是ie或者opera12以下版本
  if (canUseTextInputEvent) {
    // getNativeBeforeInputChars获取'topCompositionEnd'事件的输入文本、'topKeyPress'事件的空格输入、'topTextInput'的其他字符输入
    chars = getNativeBeforeInputChars(topLevelType, nativeEvent);
  } else {
    // 浏览器不支持TextInput事件,通过CompositionEvent事件获取输入文本;并排除粘贴命令等
    chars = getFallbackBeforeInputChars(topLevelType, nativeEvent);
  }

  if (!chars) {
    return null;
  }

  var event = SyntheticInputEvent.getPooled(eventTypes.beforeInput, targetInst, nativeEvent, nativeEventTarget);

  event.data = chars;
  EventPropagators.accumulateTwoPhaseDispatches(event);
  return event;
}

var BeforeInputEventPlugin = {

  eventTypes: eventTypes,

  extractEvents: function (topLevelType, targetInst, nativeEvent, nativeEventTarget) {
    return [
      // 构建中文等特殊输入法时的CompositionEvent合成事件对象
      extractCompositionEvent(topLevelType, targetInst, nativeEvent, nativeEventTarget),
      // 构建向输入框插值操作的beforeInput合成事件对象
      extractBeforeInputEvent(topLevelType, targetInst, nativeEvent, nativeEventTarget)
    ];
  }
};

module.exports = BeforeInputEventPlugin;

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值