A. Product change

1. Event delegate is no longer attached to document

The main problem with the React event is the default delegate mechanism of the React event system. For performance reasons, React will only put event listener on document, and then bubble into document after DOM event is triggered. React finds the corresponding component. Create a React event (SyntheticEvent) and simulate the event bubbles through the component tree (native DOM events are already popping out of the Document). As a result, e.topPropagation () does not work properly when the React components are nested. If a nested tree has stopped propagation of an event, the outer tree would still receive it.

To fix this, React 17 instead of attaching event delegates to documents, attaches them to DOM containers:

react 17 delegation

const rootNode = document.getElementById('root');
// Take render as an example
ReactDOM.render(<App />, rootNode);
// Portals are the same
// ReactDOM.createPortal(<App />, rootNode)
// React 16 event delegate (attach to document)
document.addEventListener()
// React 17 event delegate (attach to DOM container)
rootNode.addEventListener()
Copy the code

React 16 Event system

1. React Event system

React Implements a SyntheticEvent layer based on the Virtual DOM. The event handler we define will receive an instance of the SyntheticEvent object, which also supports the event bubble mechanism. We can interrupt it using stopPropagation() and preventDefault().

1-2. All events are automatically bound to the outermost layer (document)

2. Synthesize event binding mechanisms

Under React, two main things are done for composite events: event delegation and automatic binding.

2.1 Event Delegation

React does not bind event handlers directly to real nodes. Instead, it binds all events to the outermost layer of the structure, using a unified event listener that maintains a mapping of event listeners and handlers inside all components.

When a component is mounted or unmounted, only objects are inserted or removed from this unified event listener; When an event occurs, it is first handled by the unified event listener, and then the actual event handler is found in the map and called.

This simplifies event handling and recycling mechanisms, and improves efficiency.

2.2 Automatic Binding

In the React component, the context of each method points to an instance of that component, which automatically binds this to the current component. React also caches these references to optimize CPU and memory.

3. Use native events in React

React provides a full-fledged lifecycle approach, where componentDidMount is called after the component has been installed and the actual DOM exists in the browser, at which point we can bind native events. React does not automatically manage native events, so you need to unregister native events when uninstalling components.

4. Mix composite events with native events
  • Do not mix composite events with native events

  • Avoid it by e.target judgment

    Use reactEvent. NativeEvent. StopPropagation () to prevent a bubble. Preventing React events from bubbling can only be used in the React composite event system, and there is no way to prevent native events from bubbling. Conversely, preventing bubbling in a native event prevents the React composite event from propagating.Copy the code
React stopPropagation and stopImmediatePropagation

The event object in the React callback function is a SyntheticEvent synthesized by React, which is different from the event of the native DOM event. Specifically, in React, e.ativeEvent is the event of the native DOM event.

React composite event and native event execution sequence diagram

  • The React composite event is triggered only when DOM events bubble to document. Therefore, the React composite event object’s E.propagation can only prevent the React simulated events from bubble, but not the real DOM events from bubble
  • Preventing DOM events from bubbling also prevents compositing events because DOM event bubbling prevents events from propagating to the document
  • When the composite event and the DOM event are both bound to the document, the React process is that the composite event should be put in first so it fires first, and in that case, The stopImmediatePropagation of native event objects prevents further triggering of Document DOM events

To prevent bubbles on composite events and native events except for those on the outermost document, judge e.target to avoid them, with the following code:

document.body.addEventListener('click'.e= > {   
    if (e.target && e.target.matches('div.code')) {  
      return; }}Copy the code
6. The source code

Event registration converts React events to DOM native events and registers callbacks at the Document node.

6.1 registered
// enqueuePutListener is responsible for event registration.
// inst: Registers the React component instance of the event
// registrationName: React events, such as onClick and onChange
// Listener: React callback method bound to an event, such as handleClick and handleChange
// Transaction: React transaction flow
function enqueuePutListener(inst, registrationName, listener, transaction) {... .// doc is the found document node
    var doc = isDocumentFragment ? containerInfo._node : containerInfo._ownerDocument;
    // Event registration
    listenTo(registrationName, doc);
    // Event store, which we'll talk about later, stores event callback methods
    transaction.getReactMountReady().enqueue(putListener, {
        inst: inst,
        registrationName: registrationName,
        listener: listener
    });
}
  
Copy the code

How do I bind DOM native events on a Document

// Event registration
// registrationName: React event name, such as onClick and onChange
ContentDocumentHandle: THE DOM node to bind events to
listenTo: function (registrationName, contentDocumentHandle) {
    // document
    var mountAt = contentDocumentHandle;      
    // React events and topevents bound to the root node, such as: onClick -> topClick
    var dependencies = EventPluginRegistry.registrationNameDependencies[registrationName];
    
    for (var i = 0; i < dependencies.length; i++){
        // There are a lot of steps to determine browser compatibility, etc
        var dependency = dependencies[i];
        
        // Convert topEvent to native DOM events
        if (topEventMapping.hasOwnProperty(dependency)) {
            // The three parameters are topEvent, native DOM Event and Document
            // Bind events to the bubbling phasetrapBubbledEvent(dependency, topEventMapping[dependency], mountAt); }}}Copy the code

Specific code to bind events to the bubbling phase:

// Three parameters are topEvent, native DOM Event, Document (mount node).
trapBubbledEvent: function (topLevelType, handlerBaseName, element) {
    if(! element) {return null;
    }
    return EventListener.listen(element, handlerBaseName, ReactEventListener.dispatchEvent.bind(null, topLevelType));
}

// Three parameters are Document (mount node), native DOM Event, Event binding function
listen: function listen(target, eventType, callback) {
    // Remove the browser-compatible part, leaving the core behind
    target.addEventListener(eventType, callback, false);
    // Returns a function to unbind
    return {
        remove: function remove() {
            target.removeEventListener(eventType, callback, false); }}}Copy the code
6.2 storage

After the event is registered, you also need to store the event-bound callback function. In this way, after the event is triggered, the callback can be found to trigger. As we saw in the initial code, the putListener method is used for event callback storage.

// inst: Registers the React component instance of the event
// registrationName: React events, such as onClick and onChange
// Listener: React callback method bound to an event, such as handleClick and handleChange
putListener: function (inst, registrationName, listener) {
    // The core code is as follows
    // Generate a unique identifier key for each component instance
    var key = getDictionaryKey(inst);
    // Get some React event object in the callback store bank
    var bankForRegistrationName = listenerBank[registrationName] || (listenerBank[registrationName] = {});
    bankForRegistrationName[key] = listener;
}
Copy the code
6.3 perform

Each triggering event execution on the root node addEventListener registered callback, namely ReactEventListener. DispatchEvent method, event distribution entry function. The main business logic of this function is as follows:

Find the React Component and the DOM triggered by the event. From the React Component, call the findParent method to iterate over all the parent components in the array. From the React event store to the last parent component, use the React event name + component key to find the corresponding binding callback method and execute it.

Construct React synthetic events from DOM events. Queue the synthesized event. Events in a batch queue (including previously unprocessed, first-in-first processing) React synthesized events are not actually bubbling, but traversal of nodes.