One, foreword

Recently, I have been reading the react and reactDom source code, and I am deeply impressed by Facebook. In this process, I not only learned react source code, but also had a brand new understanding of JS, which led to the feeling that JavaScript can be written like this. In addition, in order to deepen the understanding of React, some articles have been written to record the concept and principle of React Fiber, how react Fiber is generated, the DIff algorithm after The introduction of React Fiber, and the Render stage of react source code. Then I met with a company recently, and the interviewer asked me a question about how React prevented the event from spreading. Then I quickly recalled it and found that I had not read the source code of this part. As a Buddhist, I told him that I was not very clear, I need to look at the source code. After the interview, in order to solve the confusion, I quickly went to the side of the source code, the following content is to introduce reactDom is how to implement a set of events system.

How does native JS prevent event propagation

Before reading the reactDom event source code, it is important to review the content of the native JS event loop. Veteran drivers can skip to the next chapter

What is an event loop

Event loops include event propagation and event listening. Event propagation simply means that when an event, such as a mouse click, is triggered in a page, the event propagates along the DOM. Take the dom structure below

<div>
  <p>
    <span><span/>
  <p/>
<div/>
Copy the code

Triggering a click event is propagated along div –> P –> SPAN –> P –> div, a phenomenon we call event propagation. And when we register a click event listener on the SPAN, it looks like this

Document. The getElementsByTagname (' span) [0]. AddEventListener (' click 'function callBack (e) {the console. The log (' click the span')})Copy the code

The callBack function we pass in is triggered when the event propagates to a span we call the target in event propagation. In the example above, the span is the last child node, and we use this span as the node to split event propagation into two phases

  1. The first stage is from div –> P –> SPAN and is called the capture stage
  2. The second stage is from SPAN –> P –> div and is called the bubbling stage

If a click listener is registered on the p node, will the listener trigger two callbacks? Obviously not. Js will only trigger the listener’s callback function at one stage, so which stage? This depends on the third argument to the addEventListener, which can take a Boolean value useCapture, indicating whether the listener fires during capture, or an object option. Here’s an example using a Boolean value. When useCapture is true, p’s listener fires during the capture phase, and when useCapture is false, it fires during the bubble phase.

How can events be prevented from spreading

Now there is a requirement, again in the example above, that we register the click event listener on both span and p, and that the useCapture is false, that is, fired during the bubble phase. The following

Document. The getElementsByTagname (' span) [0]. AddEventListener (' click 'function callBack (e) {the console. The log (' click the span')}, False) document. The getElementsByTagname (" p ") [0]. AddEventListener (' click 'function callBack (e) {the console. The log (' click the p')}, false)Copy the code

If we click on span, we only want to trigger listeners of span, not listeners that trigger P, how to handle that? Js also provides an API. The listener’s callback function receives an event parameter that represents the event instance being propagated. There are two methods, stopPropagation and stopImmediatePropagation, which can prevent events from propagating upward or downward.

E.s topPropagation () and e.s topImmediatePropagation difference ()

So what’s the difference between stopPropagation and Stopdynamic Propagation? Let’s start with an example

Document. The getElementsByTagname (' span) [0]. AddEventListener (' click 'function callBack1 (e) {the console. The log (' click the span1')}, false) document.getElementsByTagname('span')[0].addEventListener('click', Function callBack2(e) {console.log(' span2')}, false) document.getElementsByTagname('span')[0].addEventListener('click', Function callBack3(e) {console.log(' span1')}, False) document. The getElementsByTagname (" p ") [0]. AddEventListener (' click 'function callBack4 (e) {the console. The log (' click the p')}, false)Copy the code

Case we registered in span three click event listener, if again call in callback2 e.s topPropagation (), callBack1, callback2, callBack3 will perform, CallBack4 was not executed because event propagation was blocked. If we call the e.s topImmediatePropagation (), so callBack3 and callBack4 won’t execute. That is, stopImmediatePropagation will prevent the current DOM registered listener from being fired, and later registered listeners, and in the case of callBack3 registered after callBack2.

How to implement reactDom event system

React implements its own event system based on the JS event loop. The purpose is to resolve compatibility issues between browsers. Here’s how the React event is implemented.

Register events at the root node

First of all, reactDom applications to create the root node of the listenToAllSupportedEvents will go to call this methodAllNativeEvents is an array of all events supported by the browser. It iterates through the array and calls listenToNativeEvent based on the event type. The difference is the second parameter, which represents capture or bubble. The node that registers the event is the root node of the application, the second parameter passed in reactdom.render ().

(Note that events that support both bubbling and capturing register both listeners.)

In addition, you can find the selectionChange event registered on the document

ListenToNativeEvent will eventually register the corresponding event listener with addEventListener. The important thing is to register the listener’s callback function. In the code you can see the listener is returned by the createEventListenerWrapperWithPriority methodThen look at the createEventListenerWrapperWithPriorityReact returns different listeners based on the event type. But ultimately the listener calls the same function, dispatchEventsForPluginsYou can see very clearly that the publish subscribe model is used here, with the extractEvent$5 subscription message and processDispatchQueue publishing message

The root node registers the event callback function

1. The subscription

Finally, React runs the following codeIt’s going to want to push an object into the dispatchQueue queue that has the Event and Listenners properties, look at those two properties

  1. An event is an instance of the SyntheticEnentCtor class (there are different types of SyntheticEnentCtor depending on the type of event). Let’s take the Click event as an example

As you can see, this event is actually an object similar to a JS event, with a pointer to the native Event object. 2. Listeners are queues of callbacks that need to be executed for the eventYou can seeReact will traverse up from the target fiber in this event loopIf it is a capture event, a captureListener is inserted in front of the listeners. Otherwise, insert bubbkeListener at the end of listeners.This follows the catch, then bubble rule.

The captureListener and bubbkeListener here are the corresponding onClick functions we wrote in JSX

Release 2.

React registers events with the root node during initialization, invokes listener callback functions when an event is triggered, and then loops up from the target fiber to collect the callback function required for the event, pushing it to the dispatchQueue for execution.The dispatchQueue is already queued according to the catch then bubble rule, so you just need to loop.

3. How does react prevent composite events from spreading

How do you prevent react events from spreading? In the subscription phase, React instantiates an event containing the stopPropagation function. This function does two things:

  1. Prevents nativeEvent event propagation
  2. Mark isPropagationStopped as true

If isPropagationStopped is true, you exit the loop and interrupt event propagation.

Hybrid scenarios of reactDom events and native events

The React event system

  1. Register all supported events with the React application root node
  2. When the event is emitted, it is caught or bubbled to the root node, calling the callback function
  3. Collect the callback function corresponding to the React event to execute this time
  4. Execute the callback function corresponding to the react event to be executed this time
  5. Native events continue to bubble up or catch down

That is, when we call stopPropagation for react events, the event has already bubbled or captured the root node, as in the following exampleWhen we click on the span TAB and do not want to bubble to P, we will find that parent is printed first and child is printed later when we call stopPropagation on span onClick. In other words, by the time E.TopPropagation is called, the event has already bubbled to the root node and p’s listener callback has already been executed. So how to stop it?

  1. Delegate events to the ancestor of the root node, such as document.body and document
  2. It is also possible to delegate events to the root node and stop them from propagating through stopImmediatePropagation, provided that events are listened on after component rendering is complete