synthetic event
一、作用
二、导出常量
1. 合成事件
export const SyntheticEvent = createSyntheticEvent(EventInterface);
2. 合成用户界面事件
export const SyntheticUIEvent = createSyntheticEvent(UIEventInterface);
3. 合成鼠标事件
export const SyntheticMouseEvent = createSyntheticEvent(MouseEventInterface);
4. 合成拖拽事件
/**
* @interface DragEvent
* @see http://www.w3.org/TR/DOM-Level-3-Events/
*/
const DragEventInterface: EventInterfaceType = {
...MouseEventInterface,
dataTransfer: 0,
};
export const SyntheticDragEvent = createSyntheticEvent(DragEventInterface);
5. 合成焦点事件
/**
* @interface FocusEvent
* @see http://www.w3.org/TR/DOM-Level-3-Events/
*/
const FocusEventInterface: EventInterfaceType = {
...UIEventInterface,
relatedTarget: 0,
};
export const SyntheticFocusEvent = createSyntheticEvent(FocusEventInterface);
6. 合成动画事件
/**
* @interface Event
* @see http://www.w3.org/TR/css3-animations/#AnimationEvent-interface
* @see https://developer.mozilla.org/en-US/docs/Web/API/AnimationEvent
*/
const AnimationEventInterface: EventInterfaceType = {
...EventInterface,
animationName: 0,
elapsedTime: 0,
pseudoElement: 0,
};
export const SyntheticAnimationEvent = createSyntheticEvent(
AnimationEventInterface,
);
7. 合成剪切板事件
/**
* @interface Event
* @see http://www.w3.org/TR/clipboard-apis/
*/
const ClipboardEventInterface: EventInterfaceType = {
...EventInterface,
clipboardData: function (event) {
return 'clipboardData' in event
? event.clipboardData
: window.clipboardData;
},
};
export const SyntheticClipboardEvent = createSyntheticEvent(
ClipboardEventInterface,
);
8. 合成组成事件
/**
* @interface Event
* @see http://www.w3.org/TR/DOM-Level-3-Events/#events-compositionevents
*/
const CompositionEventInterface: EventInterfaceType = {
...EventInterface,
data: 0,
};
export const SyntheticCompositionEvent = createSyntheticEvent(
CompositionEventInterface,
);
9. 合成输入事件
/**
* @interface Event
* @see http://www.w3.org/TR/2013/WD-DOM-Level-3-Events-20131105
* /#events-inputevents
*/
// Happens to share the same list for now.
export const SyntheticInputEvent = SyntheticCompositionEvent;
10. 合成键盘事件
/**
* @interface KeyboardEvent
* @see http://www.w3.org/TR/DOM-Level-3-Events/
*/
const KeyboardEventInterface: EventInterfaceType = {
...UIEventInterface,
key: getEventKey,
code: 0,
location: 0,
ctrlKey: 0,
shiftKey: 0,
altKey: 0,
metaKey: 0,
repeat: 0,
locale: 0,
getModifierState: getEventModifierState,
// Legacy Interface
// 旧接口
charCode: function (event: { [propName: string]: mixed }) {
// `charCode` is the result of a KeyPress event and represents the value of
// the actual printable character.
// `charCode` 是 KeyPress 事件的结果,表示实际可打印字符的值。
// KeyPress is deprecated, but its replacement is not yet final and not
// implemented in any major browser. Only KeyPress has charCode.
//
// KeyPress 已被弃用,但其替代方案尚未确定,也未在任何主流浏览器中实现。
// 只有 KeyPress 有 charCode。
if (event.type === 'keypress') {
return getEventCharCode(event);
}
return 0;
},
keyCode: function (event: { [propName: string]: mixed }) {
// `keyCode` is the result of a KeyDown/Up event and represents the value of
// physical keyboard key.
// `keyCode` 是 KeyDown/Up 事件的结果,表示物理键盘按键的值。
// The actual meaning of the value depends on the users' keyboard layout
// which cannot be detected. Assuming that it is a US keyboard layout
// provides a surprisingly accurate mapping for US and European users.
// Due to this, it is left to the user to implement at this time.
//
// 该值的实际含义取决于用户的键盘布局,但无法检测到。假设是美式键盘布局,对于美国和
// 欧洲用户来说,这提供了出乎意料的准确映射。因此,目前由用户自行实现。
if (event.type === 'keydown' || event.type === 'keyup') {
return event.keyCode;
}
return 0;
},
which: function (event: { [propName: string]: mixed }) {
// `which` is an alias for either `keyCode` or `charCode` depending on the
// type of the event.
// `which` 是 `keyCode` 或 `charCode` 的别名,取决于事件的类型。
if (event.type === 'keypress') {
return getEventCharCode(event);
}
if (event.type === 'keydown' || event.type === 'keyup') {
return event.keyCode;
}
return 0;
},
};
export const SyntheticKeyboardEvent = createSyntheticEvent(
KeyboardEventInterface,
);
11. 合成指针事件
/**
* @interface PointerEvent
* @see http://www.w3.org/TR/pointerevents/
*/
const PointerEventInterface: EventInterfaceType = {
...MouseEventInterface,
pointerId: 0,
width: 0,
height: 0,
pressure: 0,
tangentialPressure: 0,
tiltX: 0,
tiltY: 0,
twist: 0,
pointerType: 0,
isPrimary: 0,
};
export const SyntheticPointerEvent = createSyntheticEvent(
PointerEventInterface,
);
12. 合成提交事件
/**
* @interface SubmitEvent
* @see https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#the-submitevent-interface
*/
const SubmitEventInterface: EventInterfaceType = {
...EventInterface,
submitter: 0,
};
export const SyntheticSubmitEvent: $FlowFixMe =
createSyntheticEvent(SubmitEventInterface);
13. 合成触控事件
/**
* @interface TouchEvent
* @see http://www.w3.org/TR/touch-events/
*/
const TouchEventInterface: EventInterfaceType = {
...UIEventInterface,
touches: 0,
targetTouches: 0,
changedTouches: 0,
altKey: 0,
metaKey: 0,
ctrlKey: 0,
shiftKey: 0,
getModifierState: getEventModifierState,
};
export const SyntheticTouchEvent = createSyntheticEvent(TouchEventInterface);
14. 合成过渡事件
/**
* @interface Event
* @see http://www.w3.org/TR/2009/WD-css3-transitions-20090320/#transition-events-
* @see https://developer.mozilla.org/en-US/docs/Web/API/TransitionEvent
*/
const TransitionEventInterface: EventInterfaceType = {
...EventInterface,
propertyName: 0,
elapsedTime: 0,
pseudoElement: 0,
};
export const SyntheticTransitionEvent = createSyntheticEvent(
TransitionEventInterface,
);
15. 合成轮事件
/**
* @interface WheelEvent
* @see http://www.w3.org/TR/DOM-Level-3-Events/
*/
const WheelEventInterface: EventInterfaceType = {
...MouseEventInterface,
deltaX(event: { [propName: string]: mixed }) {
return 'deltaX' in event
? event.deltaX
: // Fallback to `wheelDeltaX` for Webkit and normalize (right is positive).
// 回退到 `wheelDeltaX` 以支持 Webkit,并进行归一化(右方向为正)。
'wheelDeltaX' in event
? -event.wheelDeltaX
: 0;
},
deltaY(event: { [propName: string]: mixed }) {
return 'deltaY' in event
? event.deltaY
: // Fallback to `wheelDeltaY` for Webkit and normalize (down is positive).
// 回退到 `wheelDeltaY` 以支持 Webkit,并进行归一化(向下为正)。
'wheelDeltaY' in event
? -event.wheelDeltaY
: // Fallback to `wheelDelta` for IE<9 and normalize (down is positive).
// 对于 IE<9 使用 `wheelDelta` 回退并进行归一化(向下为正)。
'wheelDelta' in event
? -event.wheelDelta
: 0;
},
deltaZ: 0,
// Browsers without "deltaMode" is reporting in raw wheel delta where one
// notch on the scroll is always +/- 120, roughly equivalent to pixels.
// A good approximation of DOM_DELTA_LINE (1) is 5% of viewport size or
// ~40 pixels, for DOM_DELTA_SCREEN (2) it is 87.5% of viewport size.
// 没有 “deltaMode” 的浏览器会报告原始滚轮增量,其中滚动一格总是 ±120,大致相当于像素。
// 对于 DOM_DELTA_LINE (1),一个较好的近似值是视口大小的 5% 或约 40 像素,
// 对于 DOM_DELTA_SCREEN (2),则是视口大小的 87.5%.
deltaMode: 0,
};
export const SyntheticWheelEvent = createSyntheticEvent(WheelEventInterface);
16. 合成切换事件
const ToggleEventInterface: EventInterfaceType = {
...EventInterface,
newState: 0,
oldState: 0,
};
export const SyntheticToggleEvent = createSyntheticEvent(ToggleEventInterface);
三、常量
1. 事件接口
/**
* @interface Event
* @see http://www.w3.org/TR/DOM-Level-3-Events/
*/
const EventInterface: EventInterfaceType = {
eventPhase: 0,
bubbles: 0,
cancelable: 0,
timeStamp: function (event: { [propName: string]: mixed }) {
return event.timeStamp || Date.now();
},
defaultPrevented: 0,
isTrusted: 0,
};
2. 用户界面事件接口
const UIEventInterface: EventInterfaceType = {
...EventInterface,
view: 0,
detail: 0,
};
3. 鼠标事件接口
/**
* @interface MouseEvent
* @see http://www.w3.org/TR/DOM-Level-3-Events/
*/
const MouseEventInterface: EventInterfaceType = {
...UIEventInterface,
screenX: 0,
screenY: 0,
clientX: 0,
clientY: 0,
pageX: 0,
pageY: 0,
ctrlKey: 0,
shiftKey: 0,
altKey: 0,
metaKey: 0,
getModifierState: getEventModifierState,
button: 0,
buttons: 0,
relatedTarget: function (event) {
if (event.relatedTarget === undefined)
return event.fromElement === event.srcElement
? event.toElement
: event.fromElement;
return event.relatedTarget;
},
movementX: function (event) {
if ('movementX' in event) {
return event.movementX;
}
updateMouseMovementPolyfillState(event);
return lastMovementX;
},
movementY: function (event) {
if ('movementY' in event) {
return event.movementY;
}
// Don't need to call updateMouseMovementPolyfillState() here
// because it's guaranteed to have already run when movementX
// was copied.
// 这里不需要调用 updateMouseMovementPolyfillState()
// 因为当 movementX 被复制时,它已经保证运行过了。
return lastMovementY;
},
};
4. 规范化键
备注
该行代码在源码中的 309 - 326 行
/**
* Normalization of deprecated HTML5 `key` values
* @see https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent#Key_names
*/
const normalizeKey = {
Esc: 'Escape',
Spacebar: ' ',
Left: 'ArrowLeft',
Up: 'ArrowUp',
Right: 'ArrowRight',
Down: 'ArrowDown',
Del: 'Delete',
Win: 'OS',
Menu: 'ContextMenu',
Apps: 'ContextMenu',
Scroll: 'ScrollLock',
MozPrintableKey: 'Unidentified',
};
5. 翻译为键
备注
该段在源码中 328 - 370 行
/**
* Translation from legacy `keyCode` to HTML5 `key`
* Only special keys supported, all others depend on keyboard layout or browser
*
* * 从旧版 `keyCode` 转换到 HTML5 `key`。仅支持特殊按键,其他按键取决于键盘布局
* 或浏览器
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent#Key_names
*/
const translateToKey = {
'8': 'Backspace',
'9': 'Tab',
'12': 'Clear',
'13': 'Enter',
'16': 'Shift',
'17': 'Control',
'18': 'Alt',
'19': 'Pause',
'20': 'CapsLock',
'27': 'Escape',
'32': ' ',
'33': 'PageUp',
'34': 'PageDown',
'35': 'End',
'36': 'Home',
'37': 'ArrowLeft',
'38': 'ArrowUp',
'39': 'ArrowRight',
'40': 'ArrowDown',
'45': 'Insert',
'46': 'Delete',
'112': 'F1',
'113': 'F2',
'114': 'F3',
'115': 'F4',
'116': 'F5',
'117': 'F6',
'118': 'F7',
'119': 'F8',
'120': 'F9',
'121': 'F10',
'122': 'F11',
'123': 'F12',
'144': 'NumLock',
'145': 'ScrollLock',
'224': 'Meta',
};
6. 修饰键到属性
/**
* Translation from modifier key to the associated property in the event.
*
* 将修饰键转换为事件中相关的属性。
* @see http://www.w3.org/TR/DOM-Level-3-Events/#keys-Modifiers
*/
const modifierKeyToProp = {
Alt: 'altKey',
Control: 'ctrlKey',
Meta: 'metaKey',
Shift: 'shiftKey',
};
四、变量
1. 上一次移动
备注
该部分在源码的 169 - 171 行
let lastMovementX;
let lastMovementY;
let lastMouseEvent: ?{ [propName: string]: mixed };
五、工具
1. 一个返回 true 的函数
function functionThatReturnsTrue() {
return true;
}
2. 一个返回 false 的函数
function functionThatReturnsFalse() {
return false;
}
3. 创建合成事件
备注
assign()由 assign 实现
// This is intentionally a factory so that we have different returned constructors.
// If we had a single constructor, it would be megamorphic and engines would deopt.
//
// 这是故意设计成一个工厂函数,以便我们返回不同的构造函数。
// 如果我们只有一个构造函数,它将变得非常多态,执行引擎会降级优化。
function createSyntheticEvent(Interface: EventInterfaceType) {
/**
* Synthetic events are dispatched by event plugins, typically in response to a
* top-level event delegation handler.
* 合成事件由事件插件分发,通常是响应顶层事件委托处理程序。
*
* These systems should generally use pooling to reduce the frequency of garbage
* collection. The system should check `isPersistent` to determine whether the
* event should be released into the pool after being dispatched. Users that
* need a persisted event should invoke `persist`.
*
* 这些系统通常应使用对象池来减少垃圾回收的频率。系统应检查 `isPersistent` 以确定
* 事件在分发后是否应被释放回池中。需要持久化事件的用户应调用 `persist`。
*
* Synthetic events (and subclasses) implement the DOM Level 3 Events API by
* normalizing browser quirks. Subclasses do not necessarily have to implement a
* DOM interface; custom application-specific events can also subclass this.
*
* 合成事件(及其子类)通过规范浏览器差异实现了 DOM Level 3 Events API。
* 子类不一定必须实现 DOM 接口;自定义的特定应用程序事件也可以继承此类。
*/
function SyntheticBaseEvent(
reactName: string | null,
reactEventType: string,
targetInst: Fiber | null,
// nativeEvent: {[propName: string]: mixed, ...},
nativeEvent: { [propName: string]: mixed },
nativeEventTarget: null | EventTarget,
) {
this._reactName = reactName;
this._targetInst = targetInst;
this.type = reactEventType;
this.nativeEvent = nativeEvent;
this.target = nativeEventTarget;
this.currentTarget = null;
for (const propName in Interface) {
if (!Interface.hasOwnProperty(propName)) {
continue;
}
const normalize = Interface[propName];
if (normalize) {
this[propName] = normalize(nativeEvent);
} else {
this[propName] = nativeEvent[propName];
}
}
const defaultPrevented =
nativeEvent.defaultPrevented != null
? nativeEvent.defaultPrevented
: nativeEvent.returnValue === false;
if (defaultPrevented) {
this.isDefaultPrevented = functionThatReturnsTrue;
} else {
this.isDefaultPrevented = functionThatReturnsFalse;
}
this.isPropagationStopped = functionThatReturnsFalse;
return this;
}
assign(SyntheticBaseEvent.prototype, {
preventDefault: function () {
this.defaultPrevented = true;
const event = this.nativeEvent;
if (!event) {
return;
}
if (event.preventDefault) {
event.preventDefault();
} else if (typeof event.returnValue !== 'unknown') {
event.returnValue = false;
}
this.isDefaultPrevented = functionThatReturnsTrue;
},
stopPropagation: function () {
const event = this.nativeEvent;
if (!event) {
return;
}
if (event.stopPropagation) {
event.stopPropagation();
} else if (typeof event.cancelBubble !== 'unknown') {
// The ChangeEventPlugin registers a "propertychange" event for
// IE. This event does not support bubbling or cancelling, and
// any references to cancelBubble throw "Member not found". A
// typeof check of "unknown" circumvents this issue (and is also
// IE specific).
//
// ChangeEventPlugin 为 IE 注册了一个 "propertychange" 事件。
// 该事件不支持冒泡或取消,任何对 cancelBubble 的引用都会抛出
// "Member not found" 错误。对 "unknown" 进行 typeof 检查可以避免该
// 问题(这也是 IE 特有的)。
event.cancelBubble = true;
}
this.isPropagationStopped = functionThatReturnsTrue;
},
/**
* We release all dispatched `SyntheticEvent`s after each event loop, adding
* them back into the pool. This allows a way to hold onto a reference that
* won't be added back into the pool.
*
* 我们在每个事件循环后释放所有分派的 `SyntheticEvent`,并将它们重新加入池中。
* 这提供了一种方法来保留一个引用,该引用不会被重新加入池中。
*/
persist: function () {
// Modern event system doesn't use pooling.
// 现代事件系统不使用对象池。
},
/**
* Checks if this event should be released back into the pool.
* 检查是否应该将此事件释放回池中。
*
* @return {boolean} True if this should not be released, false otherwise.
* 如果不应发布,则为真,否则为假。
*/
isPersistent: functionThatReturnsTrue,
});
return SyntheticBaseEvent;
}
4. 更新鼠标移动补丁状态
function updateMouseMovementPolyfillState(event: {
[propName: string]: mixed;
}) {
if (event !== lastMouseEvent) {
if (lastMouseEvent && event.type === 'mousemove') {
lastMovementX = event.screenX - lastMouseEvent.screenX;
lastMovementY = event.screenY - lastMouseEvent.screenY;
} else {
lastMovementX = 0;
lastMovementY = 0;
}
lastMouseEvent = event;
}
}
5. 获取事件键
/**
* @param {object} nativeEvent Native browser event.
* nativeEvent 原生浏览器事件。
* @return {string} Normalized `key` property.
* 已标准化的 `key` 属性。
*/
function getEventKey(nativeEvent: { [propName: string]: mixed }) {
if (nativeEvent.key) {
// Normalize inconsistent values reported by browsers due to
// implementations of a working draft specification.
// 对浏览器报告的不一致值进行标准化,这些不一致是由于工作草案规范的实现造成的。
// FireFox implements `key` but returns `MozPrintableKey` for all
// printable characters (normalized to `Unidentified`), ignore it.
// FireFox 实现了 `key`,但对所有可打印字符返回
// `MozPrintableKey`(归一化为 `Unidentified`),忽略它。
const key = normalizeKey[nativeEvent.key] || nativeEvent.key;
if (key !== 'Unidentified') {
return key;
}
}
// Browser does not implement `key`, polyfill as much of it as we can.
// 浏览器未实现 `key`,尽可能多地进行填充。
if (nativeEvent.type === 'keypress') {
const charCode = getEventCharCode(nativeEvent);
// The enter-key is technically both printable and non-printable and can
// thus be captured by `keypress`, no other non-printable key should.
// 回车键在技术上既是可打印又是不可打印的,因此可以被 `keypress` 捕获,其他不可打印键不应如此。
return charCode === 13 ? 'Enter' : String.fromCharCode(charCode);
}
if (nativeEvent.type === 'keydown' || nativeEvent.type === 'keyup') {
// While user keyboard layout determines the actual meaning of each
// `keyCode` value, almost all function keys have a universal value.
// 虽然用户的键盘布局决定了每个 `keyCode` 值的实际含义,几乎所有功能键都有一个通用值。
return translateToKey[nativeEvent.keyCode] || 'Unidentified';
}
return '';
}
6. 修改器状态获取器
// Older browsers (Safari <= 10, iOS Safari <= 10.2) do not support
// getModifierState. If getModifierState is not supported, we map it to a set of
// modifier keys exposed by the event. In this case, Lock-keys are not supported.
//
// 较旧的浏览器(Safari <= 10, iOS Safari <= 10.2)不支持 getModifierState。
// 如果不支持 getModifierState,我们会将其映射到事件提供的一组修饰键。在这种情况下
// 不支持锁定键。
function modifierStateGetter(keyArg) {
const syntheticEvent = this;
const nativeEvent = syntheticEvent.nativeEvent;
if (nativeEvent.getModifierState) {
return nativeEvent.getModifierState(keyArg);
}
const keyProp = modifierKeyToProp[keyArg];
return keyProp ? !!nativeEvent[keyProp] : false;
}
7. 获取事件修饰键状态
function getEventModifierState(nativeEvent: { [propName: string]: mixed }) {
return modifierStateGetter;
}
六、类型
1. 事件接口类型
type EventInterfaceType = {
[propName: string]: 0 | ((event: { [propName: string]: mixed }) => mixed);
};