阅读 260

React 如何封装原生事件对象

这篇文章的目的:看 react 如何在一个具有不同类型的原生对象的基础上封装一个可扩展的对象,更多

  • 被封装的原生对象: 浏览器的原生事件对象 nativeEvent
  • 封装后的构造函数: 合成事件对象 SyntheticEvent

首先,确定需要用到那些原生对象上的属性,以及那些属性需要做特殊处理,比如兼容问题,比如降级处理等的给:

const 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 的构造函数

首先将入参设置到实例对象上,可以不关注入参的含义

  this.dispatchConfig = dispatchConfig;
  this._targetInst = targetInst; 
  this.nativeEvent = nativeEvent;
复制代码

然后通过 for in 以及 hasOwnProperty 来遍历 EventInterface 的自身属性,为了增加扩展性,先做如下处理:

SyntheticEvent.Interface = EventInterface;
复制代码

然后在 SyntheticEvent 的构造函数中通过 this.constructor.Interface 拿到这个 EventInterface,接着开始按照EventInterface为实例对象设置属性。

  for (const propName in Interface) {
    if (!Interface.hasOwnProperty(propName)) {
      continue;
    }
    const normalize = Interface[propName];
    if (normalize) {
      //如果这个自身属性值不是null则是一个函数,因此将原生事件作为参数传入,并返回结果给对应属性
      //  用于设置curreTarget与相关的时间
      this[propName] = normalize(nativeEvent);
    } else {
      if (propName === 'target') {
        //如果自身属性是'target',则将原生事件对应的DOM存储在合成事件的target属性上
        this.target = nativeEventTarget;
      } else {
        // 将原生事件的一些属性值设置到实例对象上相同名称的属性上
        this[propName] = nativeEvent[propName];
      }
    }
  }
复制代码

最后处理一下事件的默认状态,比如默认动作已经被取消,比如已经组织了事件的传递等等。isDefaultPrevented,isPropagationStopped

为 SyntheticEvent 的原型对象添加属性与方法

通过 Object.assign(SyntheticEvent.prototype,{...})的方式覆盖以及扩展原型对象上的方法与属性

  1. 与原生事件对象上相同的原型链方法:preventDefault, stopPropagation。
  2. persist 方法以及默认的 isPersistent 方法
  3. 用于清除实例对象上相关属性的方法 destructor

persist 方法以及默认的 isPersistent 方法

  persist: function() {
    this. isPersistent  = functionThatReturnsTrue;
  },
  isPersistent: functionThatReturnsFalse,
复制代码

每个合成事件对象都具备 persist 方法以及默认的 isPersistent 方法,因此放到原型对象上,persist 改变的是实例对象上的 isPersistent 属性,会屏蔽原型对象上的 isPersistent,原型对象上 isPersistent 函数默认返回 false。

destructor 清除实例对象上相关属性

  destructor: function() {
    const Interface = this.constructor.Interface;
    for (const propName in Interface) {
        this[propName] = null;
    }
    this.dispatchConfig = null;
    this._targetInst = null;
    this.nativeEvent = null;
    this.isDefaultPrevented = functionThatReturnsFalse;
    this.isPropagationStopped = functionThatReturnsFalse;
    this._dispatchListeners = null;
    this._dispatchInstances = null;
  },
复制代码

为 SyntheticEvent 添加操作事件池的静态方法

为 SyntheticEvent 添加事件池,以及操作事件池的方法 getPooledEvent 与 releasePooledEvent

function addEventPoolingTo(EventConstructor) {
  EventConstructor.eventPool = [];
  EventConstructor.getPooled = getPooledEvent;
  EventConstructor.release = releasePooledEvent;
}
addEventPoolingTo(SyntheticEvent);
复制代码

通过 SyntheticEvent 实例对象的构造函数即 event.consturctor.release能获取到 SyntheticEvent.release,该函数内部会对 SyntheticEvent.eventPool进行处理,也就是相当于在 eventPool, getPooled, release是所有SyntheticEvent实例对象共享的局部变量。

/**
 * 从事件池中获取一个空的合成事件对象,并设将入参设置到事件对象对应的属性上
 * 如果事件池为空则利用入参创建一个新的合成事件对象
 * @param {*} dispatchConfig 
 * @param {*} targetInst 
 * @param {*} nativeEvent 
 * @param {*} nativeInst 
 */
function getPooledEvent(dispatchConfig, targetInst, nativeEvent, nativeInst) {
  const EventConstructor = this;
  if (EventConstructor.eventPool.length) {
    const instance = EventConstructor.eventPool.pop();
    EventConstructor.call(
      instance,
      dispatchConfig,
      targetInst,
      nativeEvent,
      nativeInst,
    );
    return instance;
  }
  return new EventConstructor(
    dispatchConfig,
    targetInst,
    nativeEvent,
    nativeInst,
  );
}
/**
 * 重置event上的属性值,并添加到事件对象池的空余位置
 * @param {*} event 
 */
function releasePooledEvent(event) {
  const EventConstructor = this;
  invariant(
    event instanceof EventConstructor,
    'Trying to release an event instance into a pool of a different type.',
  );
  //重置合成事件对象实例
  event.destructor();
  //如果对象池还有空闲位置,添加到其中
  if (EventConstructor.eventPool.length < EVENT_POOL_SIZE) {
    EventConstructor.eventPool.push(event);
  }
}
复制代码

为 SyntheticEvent 添加 extend 静态方法增强其扩展性

对于不同类型的原生事件对象有不同的属性以及方法,因此可扩展性是非常必要的,EventInterface包含的是所有事件对象共同的属性名,所以为了需要创建一个 SyntheticEvent 的子类并且需要增加属性与方法,那么可以进行如下扩展 const SyntheticEventSubClass = SyntheticEvent.extend({clientX: null}),返回的 SyntheticEventSubClass有自己的对象池,其实例增加了 clientX 属性。此外就继承了SyntheticEvent的原型对象方法与属性。

关键点在为子类的静态属性 Interface 进行了扩展,其代码都是常规的寄生组合式继承手段。

SyntheticEvent.extend = function(Interface) {
  // SyntheticEvent.extend() 执行的时候 Super 就是这个 SyntheticEvent
  const Super = this;

  // 构造子类的原型对象
  const E = function() {};
  E.prototype = Super.prototype;
  const prototype = new E();

  // 设置原型对象以及设置构造函数
  function Class() {
    return Super.apply(this, arguments);
  }
  Object.assign(prototype, Class.prototype);
  Class.prototype = prototype;
  Class.prototype.constructor = Class;

  // 添加静态方法,扩展Interface属性
  Class.Interface = Object.assign({}, Super.Interface, Interface);
  Class.extend = Super.extend;
  addEventPoolingTo(Class);

  return Class;
};
复制代码