preface

First look at the official website for version 17 introduction:

In React V17, React no longer adds event handling to the document. Instead, React adds event handling to the root DOM container that renders the React tree:

const rootNode = document.getElementById('root');
ReactDOM.render(<App />, rootNode);
Copy the code

Before the React and 16 version, will React to the document. Most of the events addEventListener () operation. The React v17 will start by calling the rootNode. AddEventListener () instead.

Officials also provided a schematic:

Take a look at what the page event binding output from React17 looks like:Take a look at HTML again:You can see that events are being listened for at the root node and all events are being listened for. Let’s begin to dissect the event mechanism of 17.

Event mechanism

This can be summarized as event binding, handling synthesized events, collecting event response links, and triggering event callback functions.

event

Function call relationships:

graph TD
listenToAllSupportedEvents --> listenToNativeEvent --> addTrappedEventListener

Let’s look at what each function does:

listenToAllSupportedEvents

function listenToAllSupportedEvents(rootContainerElement) {
  // ...
  // allNativeEvents are allNativeEvents
  allNativeEvents.forEach(function (domEventName) {
    // Events in nonDelegatedEvents are non-bubbling elements, if the event cannot bubble, bind only the capture phase, otherwise both phases are bound
    if(! nonDelegatedEvents.has(domEventName)) { listenToNativeEvent(domEventName,false, rootContainerElement, null);
    }

    listenToNativeEvent(domEventName, true, rootContainerElement, null);
  });
}
Copy the code

listenToNativeEvent

function listenToNativeEvent(domEventName, isCapturePhaseListener, rootContainerElement, targetElement) {
  // ...
  addTrappedEventListener(target, domEventName, eventSystemFlags, isCapturePhaseListener);
  // ...
}
Copy the code

addTrappedEventListener

function addTrappedEventListener(targetContainer, domEventName, eventSystemFlags, isCapturePhaseListener, isDeferredListenerForLegacyFBSupport) {
  // Create listeners with different priorities according to the event name
  var listener = createEventListenerWrapperWithPriority(targetContainer, domEventName, eventSystemFlags); 
  // Bind the listener
  if (isCapturePhaseListener) {
    if(isPassiveListener ! = =undefined) {
      unsubscribeListener = addEventCaptureListenerWithPassiveFlag(targetContainer, domEventName, listener, isPassiveListener);
    } else{ unsubscribeListener = addEventCaptureListener(targetContainer, domEventName, listener); }}else {
    if(isPassiveListener ! = =undefined) {
      unsubscribeListener = addEventBubbleListenerWithPassiveFlag(targetContainer, domEventName, listener, isPassiveListener);
    } else{ unsubscribeListener = addEventBubbleListener(targetContainer, domEventName, listener); }}}Copy the code

What about events that do not bubble up?

It is very simple to listen directly on the DOM node where the corresponding event is bound. The code involved is as follows:

function setInitialProperties(domElement, tag, rawProps, rootContainerElement) {
  // ...
  switch (tag) {
    case 'dialog':
      listenToNonDelegatedEvent('cancel', domElement);
      listenToNonDelegatedEvent('close', domElement);
      break;

    case 'iframe':
    case 'object':
    case 'embed':
      // We listen to this event in case to ensure emulated bubble
      // listeners still fire for the load event.
      listenToNonDelegatedEvent('load', domElement);
      break;
      // ...
  }
  // ...
} // Calculate the diff between the two objects.
Copy the code

Which listenToNonDelegatedEvent method will be done to call addTrappedEventListener method event bindings. The final effect is as follows:

Handling compositing events

Call dispatchEventsForPlugins method to synthesize native events. There are many related articles on the Internet, which will not be mentioned here. The core methods involved are as follows:

function dispatchEventsForPlugins(
  domEventName: DOMEventName,
  eventSystemFlags: EventSystemFlags,
  nativeEvent: AnyNativeEvent,
  targetInst: null | Fiber,
  targetContainer: EventTarget,
) :void {
  const nativeEventTarget = getEventTarget(nativeEvent);
  const dispatchQueue: DispatchQueue = [];
  // Event object composition, collect events to the execution path
  extractEvents(
    dispatchQueue,
    domEventName,
    targetInst,
    nativeEvent,
    nativeEventTarget,
    eventSystemFlags,
    targetContainer,
  );
  Execute the actual events in the collected components
  processDispatchQueue(dispatchQueue, eventSystemFlags);
}
Copy the code

The dispatchQueue is an array of listeners, each containing a synthetic event and its corresponding callback function. The extractEvents compose the event and call the corresponding method to collect the event response link. ProcessDispatchQueue is responsible for executing the callback function for the event

Collect event response links

In this stage, it starts from the fiber node corresponding to the DOM node corresponding to the event source, traverses the parent fiber node one by one until the root fiber node, and looks for the corresponding event function in the fiber node props. For example, if the onClick event is triggered, look for onClick in the props of the fiber node. If there are corresponding event names and their functions, they are collected in order. The core method: accumulateSinglePhaseListeners, accumulateTwoPhaseListeners. The following accumulateSinglePhaseListeners, for example

function accumulateSinglePhaseListeners(targetFiber, reactName, nativeEventType, inCapturePhase, accumulateTargetOnly) {
  varcaptureName = reactName ! = =null ? reactName + 'Capture' : null;
  // Set different event names according to the initial event listening phase, for example onClick, reactName is onClick captureName is onClickCapture
  var reactEventName = inCapturePhase ? captureName : reactName;

  var listeners = [];
  var instance = targetFiber;
  var lastHostComponent = null; // Accumulate all instances and listeners via the target -> root path.

  while(instance ! = =null) {
    var _instance2 = instance,
        stateNode = _instance2.stateNode, // stateNode is the dom node corresponding to the fiber node
        tag = _instance2.tag; // Handle listeners that are on HostComponents (i.e. <div>)

    React only handles events bound to dom nodes
    if(tag === HostComponent && stateNode ! = =null) {
      lastHostComponent = stateNode; // createEventHandle listeners


      if(reactEventName ! = =null) {
        var listener = getListener(instance, reactEventName);

        if(listener ! =null) { listeners.push(createDispatchListener(instance, listener, lastHostComponent)); }}}// ...
    / / to find up
    instance = instance.return;
  }

  return listeners;
}
Copy the code
function getListener(inst, registrationName) {
  var stateNode = inst.stateNode;
  // ...
  
  // Get props on the FIBER node corresponding to the DOM node
  var props = getFiberCurrentPropsFromNode(stateNode);
  // Get the event callback function of the same name
  var listener = props[registrationName];

  // ...

  return listener;
}
Copy the code

Take the following components for example:

function App() {
  return (
    <div className="App">
      <header onClick={()= > { console.log('header') }} className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <code onClick={()= > { console.log('code') }}>src/App.js</code>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}
Copy the code

The last one I collectedlistenersAs follows: dipatchQueueAs follows:

Trigger the event callback function

In the stage of collecting event response links, a link containing event listeners has been collected, and the collection sequence is carried out from the triggering node to the root node, which is to simulate bubbling and vice versa. Core method processDispatchQueue processDispatchQueueItemsInOrder

function processDispatchQueue(dispatchQueue, eventSystemFlags) {
  varinCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) ! = =0;
  / / traverse dispatchQueue and call processDispatchQueueItemsInOrder
  for (var i = 0; i < dispatchQueue.length; i++) {
    var _dispatchQueue$i = dispatchQueue[i],
        event = _dispatchQueue$i.event,
        listeners = _dispatchQueue$i.listeners;
    processDispatchQueueItemsInOrder(event, listeners, inCapturePhase); // event system doesn't use pooling.
  } // This would be a good time to rethrow if any of the event handlers threw.


  rethrowCaughtError();
}
Copy the code
function processDispatchQueueItemsInOrder(event, dispatchListeners, inCapturePhase) {
  var previousInstance;

  / / capture
  if (inCapturePhase) {
    // Reverse execution, equivalent to capture
    for (var i = dispatchListeners.length - 1; i >= 0; i--) {
      var _dispatchListeners$i = dispatchListeners[i],
          instance = _dispatchListeners$i.instance,
          currentTarget = _dispatchListeners$i.currentTarget,
          listener = _dispatchListeners$i.listener;

      if(instance ! == previousInstance && event.isPropagationStopped()) {return; } executeDispatch(event, listener, currentTarget); previousInstance = instance; }}else { / / the bubbling
    // Execute sequentially, equivalent to bubbling
    for (var _i = 0; _i < dispatchListeners.length; _i++) {
      var _dispatchListeners$_i = dispatchListeners[_i],
          _instance = _dispatchListeners$_i.instance,
          _currentTarget = _dispatchListeners$_i.currentTarget,
          _listener = _dispatchListeners$_i.listener;

      if(_instance ! == previousInstance && event.isPropagationStopped()) {return; } executeDispatch(event, _listener, _currentTarget); previousInstance = _instance; }}}Copy the code

At this point, we have basically analyzed the event mechanism of Act17, so here’s a summary.

conclusion

  • React17 listens for the bubble and capture phases of all events on rootContainer, and events that cannot bubble are bound directly to the corresponding DOM node
  • No event listeners are bound to DOM nodes (except for a few special cases)
  • The event callback function in element is stored in the props of the Fiber node
  • When an event is listened on, the capture and bubble phases are handled on rootContainer (events that cannot bubble are handled on the corresponding DOM node), through function calls, and finally calleddispatchEventsForPluginsThe native event is processed into a composite event, and the fiber node that triggers the event is searched up for fiber nodes that are listening for the same event (events that can’t bubble are not searched up), and the callback function in the props is placed in an array for execution
  • Finally, the order of execution is determined based on whether the event is bubbling or capturing

There is a description of the wrong place hope big guy can point out, thank you ~