React事件(二)SyntheticEvent 介绍
Opened this issue · 0 comments
React 现在(16.7)有13种合成事件是直接或者间接继承SyntheticEvent的。
这些事件如下
SyntheticCompositionEvent
SyntheticInputEvent
SyntheticUIEvent
SyntheticMouseEvent
SyntheticPointerEvent
SyntheticAnimationEvent
SyntheticClipboardEvent
SyntheticFocusEvent
SyntheticKeyboardEvent
SyntheticDragEvent
SyntheticTouchEvent
SyntheticTransitionEvent
SyntheticWheelEvent
下面介绍一下SyntheticEvent
SyntheticEvent
SyntheticEvent 的会构造合成事件对象 即events
该构造函数参数如下
- dispatchConfig 见React 事件初始化
- targetInst 事件目标对应fiber node
- nativeEvent 原生事件
- nativeEventTarget 原生事件目标,即dom node
function SyntheticEvent(dispatchConfig, targetInst, nativeEvent, nativeEventTarget) {
//实例化时删除实例上的属性
{
// these have a getter/setter for warnings
delete this.nativeEvent;
delete this.preventDefault;
delete this.stopPropagation;
delete this.isDefaultPrevented;
delete this.isPropagationStopped;
}
//将事件的新属性赋值给实例
this.dispatchConfig = dispatchConfig;
this._targetInst = targetInst;
this.nativeEvent = nativeEvent;
//遍历接口对象,清空实例属性,同时设置target属性,将原生事件的属性赋值给实例
var Interface = this.constructor.Interface;
for (var propName in Interface) {
if (!Interface.hasOwnProperty(propName)) {
continue;
}
{
delete this[propName]; // this has a getter/setter for warnings
}
var normalize = Interface[propName];
if (normalize) {
this[propName] = normalize(nativeEvent);
} else {
if (propName === 'target') {
this.target = nativeEventTarget;
} else {
this[propName] = nativeEvent[propName];
}
}
}
//设置isDefaultPrevented属性
var defaultPrevented = nativeEvent.defaultPrevented != null ? nativeEvent.defaultPrevented : nativeEvent.returnValue === false;
if (defaultPrevented) {
this.isDefaultPrevented = functionThatReturnsTrue;
} else {
this.isDefaultPrevented = functionThatReturnsFalse;
}
this.isPropagationStopped = functionThatReturnsFalse;
return this;
}
刚才的Interface来自 EventInterface,这样SyntheicEvent对象都有该接口的属性,而且该接口是可以扩展的,不同子类可以实现不同的接口
SyntheticEvent.Interface = EventInterface;
var EventInterface = {
type: null,
target: null,
// currentTarget is set when dispatching; no use in copying it here
currentTarget: function () {
return null;
},
eventPhase: null,
bubbles: null,
cancelable: null,
timeStamp: function (event) {
return event.timeStamp || Date.now();
},
defaultPrevented: null,
isTrusted: null
};
SyntheticEvent.extend = function (Interface) {
var Super = this;
// 经典的寄生组合式继承
var E = function () {};
E.prototype = Super.prototype;
var prototype = new E();
function Class() {
return Super.apply(this, arguments);
}
_assign(prototype, Class.prototype);
Class.prototype = prototype;
Class.prototype.constructor = Class;
//扩展接口属性
Class.Interface = _assign({}, Super.Interface, Interface);
Class.extend = Super.extend;
//添加事件到事件池
addEventPoolingTo(Class);
return Class;
};
SyntheticEvent的原型
_assign(SyntheticEvent.prototype, {
preventDefault: function () {
//拿到浏览器的原生事件,存在preventDefault则调用preventDefault方法,否则判断有无returnValue属性(兼容IE)
this.defaultPrevented = true;
var 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 () {
var event = this.nativeEvent;
if (!event) {
return;
}
if (event.stopPropagation) {
event.stopPropagation();
} else if (typeof event.cancelBubble !== 'unknown') {
event.cancelBubble = true;
}
this.isPropagationStopped = functionThatReturnsTrue;
},
//如果您想以异步的方式访问事件的属性值,
//你必须在事件回调中调用event.persist()方法,
//这样会在池中删除合成事件,并且在用户代码中保留对事件的引用。
persist: function () {
this.isPersistent = functionThatReturnsTrue;
},
/**
* 检查是否应将此事件释放回池中
*
* 返回true 则不会.
*/
isPersistent: functionThatReturnsFalse,
/**
* 事件释放的时候清空其属性,减少内存占用
*/
destructor: function () {
var Interface = this.constructor.Interface;
for (var propName in Interface) {
{
Object.defineProperty(this, propName, getPooledWarningPropertyDefinition(propName, Interface[propName]));
}
}
this.dispatchConfig = null;
this._targetInst = null;
this.nativeEvent = null;
this.isDefaultPrevented = functionThatReturnsFalse;
this.isPropagationStopped = functionThatReturnsFalse;
this._dispatchListeners = null;
this._dispatchInstances = null;
{
Object.defineProperty(this, 'nativeEvent', getPooledWarningPropertyDefinition('nativeEvent', null));
Object.defineProperty(this, 'isDefaultPrevented', getPooledWarningPropertyDefinition('isDefaultPrevented', functionThatReturnsFalse));
Object.defineProperty(this, 'isPropagationStopped', getPooledWarningPropertyDefinition('isPropagationStopped', functionThatReturnsFalse));
Object.defineProperty(this, 'preventDefault', getPooledWarningPropertyDefinition('preventDefault', function () {}));
Object.defineProperty(this, 'stopPropagation', getPooledWarningPropertyDefinition('stopPropagation', function () {}));
}
}
});
在stopPropagation 方法中 有一句
this.isPropagationStopped = functionThatReturnsTrue;
这相当于是设置了一个标志位,对于冒泡事件来说,当事件触发,由子元素往父元素逐级向上遍历,会按顺序执行每层元素对应的事件回调,但如果发现当前元素对应的合成事件上的 isPropagationStopped为 true值,则遍历的循环将中断,也就是不再继续往上遍历,当前元素的所有父元素的合成事件就不会被触发,最终的效果,就和浏览器原生事件调用 e.stopPropagation()的效果是一样的。
function addEventPoolingTo(EventConstructor) {
EventConstructor.eventPool = [];
EventConstructor.getPooled = getPooledEvent;
EventConstructor.release = releasePooledEvent;
}
function getPooledEvent(dispatchConfig, targetInst, nativeEvent, nativeInst) {
var EventConstructor = this;
if (EventConstructor.eventPool.length) {
var instance = EventConstructor.eventPool.pop();
EventConstructor.call(instance, dispatchConfig, targetInst, nativeEvent, nativeInst);
return instance;
}
return new EventConstructor(dispatchConfig, targetInst, nativeEvent, nativeInst);
}
function releasePooledEvent(event) {
var EventConstructor = this;
!(event instanceof EventConstructor) ? invariant(false, 'Trying to release an event instance into a pool of a different type.') : void 0;
event.destructor();
if (EventConstructor.eventPool.length < EVENT_POOL_SIZE) {
EventConstructor.eventPool.push(event);
}
}
getPooledEvent方法在首次触发事件的时候 EventConstructor.eventPool.length为 0,因为这个时候是第一次事件触发,对象池中没有对应的合成事件引用,所以需要初始化,后续再触发事件的时候,就无需 new了,直接从对象池中取,通过 EventConstructor.eventPool.pop()获取合成对象实例.
releasePooledEvent方法主要做了两件事,首先释放掉 event上属性占用的内存,然后把清理后的 event对象再放入对象池中,可以被后续事件对象二次利用。
最后看一下基于SyntheticEvent的合成事件
var SyntheticInputEvent = SyntheticEvent.extend({
data: null
});
var SyntheticUIEvent = SyntheticEvent.extend({
view: null,
detail: null
});
const SyntheticMouseEvent = SyntheticUIEvent.extend({
screenX: null,
screenY: null,
clientX: null,
clientY: null,
pageX: null,
pageY: null,
...
})
SyntheticInputEvent这个合成事件有自己的属性,和浏览器原生事件有一样的接口,符合w3c标准,开头介绍的十几种事件都是直接或者间接继承自SyntheticEvent。