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;