xiaoxiaosaohuo/Note

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

该构造函数参数如下
  1. dispatchConfig 见React 事件初始化
  2. targetInst 事件目标对应fiber node
  3. nativeEvent 原生事件
  4. 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。