How React encapsulates native event objects

The purpose of this article is to see how react encapsulates an extensible object based on a native object with different types, more

  • Encapsulated native object: Browser native event object nativeEvent
  • Encapsulated constructor: synthetic event object

First, determine which properties on the native object need to be used, and which properties need special processing, such as compatibility issues, such as degradation processing

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,
};
Copy code

Building a constructor for synthetic event

First, set the input parameter to the instance object. You can ignore the meaning of the input parameter

  this.dispatchConfig = dispatchConfig;
  this._targetInst = targetInst; 
  this.nativeEvent = nativeEvent;
Copy code

Then traverse the properties of the EventInterface itself through for in and hasOwnProperty. To increase the extensibility, do the following:

SyntheticEvent.Interface = EventInterface;
Copy code

Then in the constructor of synthetic event this.constructor.Interface Get the EventInterface, and then set the properties for the instance object according to the EventInterface.

  for (const propName in Interface) {
    if (!Interface.hasOwnProperty(propName)) {
      continue;
    }
    const normalize = Interface[propName];
    if (normalize) {
      //If the property value is not null, it is a function, so the native event is passed in as a parameter and the result is returned to the corresponding property
      //  Used to set curreTarget and related time
      this[propName] = normalize(nativeEvent);
    } else {
      if (propName === 'target') {
        //If the property itself is'target',Corresponding to the native event DOM Stored in the target On attribute
        this.target = nativeEventTarget;
      } else {
        // Set some property values of the native event to properties with the same name on the instance object
        this[propName] = nativeEvent[propName];
      }
    }
  }
Copy code

Finally, handle the default state of the event, for example, the default action has been canceled, for example, the event delivery has been organized, and so on. isDefaultPrevented´╝îisPropagationStopped

Add properties and methods to the prototype object of synthetic event

Through Object.assign(SyntheticEvent.prototype , and extend methods and properties on prototype objects

  1. The same prototype chain method as on the native event object: preventDefault, stopPropagation.
  2. persist method and default isPersistent method
  3. Method destructor for clearing related properties on instance objects

persist method and default isPersistent method

  persist: function() {
    this. isPersistent  = functionThatReturnsTrue;
  },
  isPersistent: functionThatReturnsFalse,
Copy code

Each composite event object has a persist method and a default isPersistent method. Therefore, when it is placed on the prototype object, persist changes the isPersistent property on the instance object, which will mask isPersistent on the prototype object. The isPersistent function on the prototype object returns false by default.

destructor clears related properties on the instance object

  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;
  },
Copy code

Static method of adding operation event pool for synthetic event

Add event pool for synthetic event, and the methods getPooledEvent and releasePooledEvent for operating event pool

function addEventPoolingTo(EventConstructor) {
  EventConstructor.eventPool = [];
  EventConstructor.getPooled = getPooledEvent;
  EventConstructor.release = releasePooledEvent;
}
addEventPoolingTo(SyntheticEvent);
Copy code

Through the constructor of the synthetic event instance object event.consturctor.release Available SyntheticEvent.release , the function will internally SyntheticEvent.eventPool Processing, that is to say, in eventPool, getPooled, release is a local variable shared by all synthetic event instance objects.

/**
 * Get an empty composite event object from the event pool and set the input parameter to the corresponding property of the event object
 * If the event pool is empty, create a new composite event object with the input parameter
 * @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,
  );
}
/**
 * Reset the property value on event and add to the free location of the event object pool
 * @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.',
  );
  //Reset composite event object instance
  event.destructor();
  //Add to the object pool if it has free space
  if (EventConstructor.eventPool.length < EVENT_POOL_SIZE) {
    EventConstructor.eventPool.push(event);
  }
}
Copy code

Adding extend static methods to synthetic event to enhance its extensibility

There are different properties and methods for different types of native event objects, so extensibility is very necessary. EventInterface contains the common property names of all event objects, so in order to create a subclass of synthetic event and add properties and methods, you can extend const synthetic event subclass as follows= SyntheticEvent.extend ({clientX: null}), the returned SyntheticEventSubClass has its own object pool. In fact, the clientX attribute is added to the example. In addition, it inherits the prototype object methods and properties of synthetic event.

The key point is to extend the static attribute Interface of the subclass, whose code is the general parasitic combination inheritance method.

SyntheticEvent.extend = function(Interface) {
  // SyntheticEvent.extend() Super is the synthetic event when executing
  const Super = this;

  // Construct prototype objects of subclasses
  const E = function() {};
  E.prototype = Super.prototype;
  const prototype = new E();

  // Set prototype object and set constructor
  function Class() {
    return Super.apply(this, arguments);
  }
  Object.assign(prototype, Class.prototype);
  Class.prototype = prototype;
  Class.prototype.constructor = Class;

  // Add static methods, extend Interface properties
  Class.Interface = Object.assign({}, Super.Interface, Interface);
  Class.extend = Super.extend;
  addEventPoolingTo(Class);

  return Class;
};
Copy code

Tags: Attribute React

Posted on Sun, 31 May 2020 16:52:06 -0700 by graham