Sorry for the link failure in the gold digging page, it will be corrected later

Look at the original

TL; DR

This article analyzes the React event system and source code, and answers the question “Why does React need to implement its own event system?” “And” How does the React event system work? Two questions. React implements its own event system by using event proxy, pooling, batch update, cross-browser and cross-platform compatibility, attaching event listeners to Document, constructing composite events, and internally emulating a mechanism for capturing and bubbling and triggering callback functions.

  • If you only have a few minutes, I suggest you go straight to the animation.
  • If you have half an hour, you can read it in order, ignoring the source section.
  • If you are interested in the React event system, it is recommended that you clone the React source code (the source code cited here comes from V16.5.0) and read it in sequence.

start

Recently, I and my colleagues encountered some strange problems when refactoring the front end of a project using React. Therefore, I spent some time researching the React source code. The topic of this article is the React event system. I try to eliminate complex technical details, and hope to answer two questions in a simple and intuitive way: ** “Why does React need to implement its own event system?” “And” How does the React event system work? * *.

Stuff can sometimes get surprisingly messy if you don’t know how it works…

Two simple examples

Example a

  1. According to the following code, what will be the output after the button is clicked? (ABCD)

  2. If I add e.topPropagation () to innerClick, what will be the output? (ABCD)

class App extends React.Component {
  innerClick = e= > {
    console.log("A: react inner click.");
    // e.stopPropagation();
  };

  outerClick = (a)= > {
    console.log("B: react outer click.");
  };

  componentDidMount() {
    document
      .getElementById("outer")
      .addEventListener("click", () = >console.log("C: native outer click"));

    window.addEventListener("click", () = >console.log("D: native window click")); } render() {return (
      <div id="outer" onClick={this.outerClick}>
        <button id="inner" onClick={this.innerClick}>
          BUTTON
        </button>
      </div>); }}Copy the code

The correct answer is (in case you peek, swipe left < –) :

                                                                                            1. 
                                                                                                C: native outer click 
                                                                                                A: react inner click. 
                                                                                                B: react outer click. 
                                                                                                D: native window click 
                                                                                            2.
                                                                                                C: native outer click 
                                                                                                A: react inner click.
Copy the code

Example 2

A form that is expected to be edited only after the edit button is clicked, and the Edit button changes to submit. Click submit to submit the form. The following code

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      editable: false
    };
  }
  handleClick = (a)= > {
    console.log("edit button click!!");
    this.setState({ editable: true });
  };
  handleSubmit = e= > {
    console.log("submit event!!");
    e.preventDefault(); // Avoid page refresh
  };
  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        {this.state.editable ? (
          <button type="submit">submit</button>
        ) : (
          <button type="button" onClick={this.handleClick}>edit</button>
        )}
      </form>); }}Copy the code

In fact, we found that clicking the Edit button already triggered the Form’s Submit event. Why is the Submit event triggered when we click a button of type=”button”?

With these two examples in mind, we move on to the subject of this article. I just want to get straight to the answer, okay?

React why implement an event system?

I think this issue is primarily about performance and reuse.

First of all, React, as a framework at the View level, obtains vDOM through rendering, and then diff algorithm determines which nodes in the DOM tree need to be added, replaced or modified. If native event listeners are directly inserted into DOM nodes, AddEventListener and removeEventListener are frequently called, wasting performance. React uses the Event broker method. For most events, it listens on documents and determines which nodes the Event triggers based on the target in the Event. (Except for a few events that do not bubble to document, such as video, etc.)

Secondly, SyntheticEvent synthesized by React adopts the idea of pool, so as to save memory and avoid frequent creation and destruction of event objects. This is why if we need to use a syntheticEvent asynchronously, we need to execute event.persist() to prevent the event object from being released.

React stores batch updates and flush them at the end of the process. Ele.style. color=’red’; Immediately after, it just wraps up the operations and eventually renders when it needs to be rendered.

ele.style.color='red'; 
ele.style.color='blue';
ele.style.color='red'; The browser only renders onceCopy the code

For reuse, React sees user interface events that are very similar across browsers and platforms, such as common click, change, and so on. React wants to encapsulate all native events on different platforms as syntheticevents by encapsulating a layer of event system.

  • makeDifferent platforms can use the same event system simply by adding EventEmitter and the corresponding RendererJoin on the WEB platformReactBrowserEventEmitterNative joinReactNativeEventEmitter. React replaces only the left part and the right part for different platformsEventPluginHubParts can be kept reusable.
  • whileReact helps unify events across different browsers, making it browser compatible, for example, fortransitionEnd.webkitTransitionEnd.MozTransitionEndandoTransitionEndReact will be combinedtopAnimationEnd, so we can only handle this one standard event.

In short, just as jQuery helped us solve compatibility issues across browsers, React goes a step further and helps unify compatibility across platforms, allowing us to only consider standardized events when developing.

How does the React event system work?

event

Let’s look at how the onClickhandler we wrote in JSX is logged to a DOM node and listens on a Document.

React registers most event bindings using trapBubbledEvent and trapCapturedEvent. As shown above, after render or setState is executed, the React Fiber scheduling system will execute trapBubbledEven or trapCapturedEvent before the final commit to the DOM tree. Bind the corresponding Dispatch to the Document node by executing addEventListener as a handler responsible for listening for events of type topLevelType.

The dispatchInteractiveEvent (dispatchEvent) and the dispatchEvent (dispatchInteractiveEvent) call-back functions are replaced by Stack Reconciliation with Fiber to enable asynchronous rendering. I still need to use the API beginning with unstable_, which is related to Example 2 and will be illustrated at the end of this article), so in the case of asynchronous rendering I clicked the button twice, so the second time the button responded, If the setState called in the first button’s handler has not been committed to the DOM tree, flush the first button’s result and commit it to the DOM tree to maintain consistency. This is where dispatchInteractiveEvent is used. Before executing dispatchInteractiveEvent, ensure that all previous operations are committed to the DOM tree, then start its own process and trigger dispatchEvent. However, since React is still rendered synchronously, the two functions currently behave identically. Hopefully, React17 will bring us asynchronous rendering enabled by default.

So now that we’ve listened for events on the Document node, we need to see how to store the handler that we wrote in JSX to the appropriate node.

Each time we create or update a node, React ends up calling createInstance or commitUpdate, and both functions end up calling updateFiberProps, which is the function onClick, The onChange handler is stored in the DOM node.

At this point, we have listened for events on the Document and stored the handler in the corresponding DOM node. React listens for and handles the browser’s native events, and eventually triggers the corresponding handler.

Events trigger

I’ve made a little animation here, which hopefully will help you understand. Click the green button > Play Next.

Sorry to insert links, nuggets do not allow iframe insertion.

Take the simple Click event as an example. We’ve already listened for the Click event on document via event binding. How does the native event get into React’s jurisdiction when we actually click the button? How to compose syntheticEvents and how to simulate capture and bubbling? And how did the onClickhandler we wrote in JSX finally get triggered? With these questions in mind, let’s take a look at the event triggering phase.

I’ll parse the code roughly as shown below, with the js call stack on the left after I click on a button bound to handleClick, and the code on the right for each step, with some code removed that doesn’t affect understanding. Hopefully this will make it easier to understand React’s event triggering mechanism.

When we click on a button, the Click event will eventually bubble up to the document and trigger us to listen for handler dispatchEvent on the document, followed by batchedUpdates. The code of batchedUpdates format will appear frequently in the source code of React. Basically, React will collect all things that can be processed in batches and then process them at once.

As you can see, isBatching is false by default. When batchedUpdates are called once, isBatching will be true. If batchedUpdates are called again in subsequent calls, HandleTopLevel is executed directly, and setState etc. is not updated to the DOM. All results are flushed (updated to the DOM) together until the call stack goes back to the first call to batchedUpdates.

What is BatchedUpdates$1 in the call stack? Or is the browser renderer and Native renderer mounted on the React event system?

Actually React event system provides a function setBatchingImplementation inside, used to dynamic renderer mount different platforms, this also reflected the reuse of the React event system.

The difference between active updates and batchedUpdates here was explained above and won’t be repeated here.

HandleTopLevel invokes runExtractedEventsInBatch (), which is a function of the React event handling is the most important. As we saw in the above animation, what we’re doing in EventEmitter is essentially two steps of this function.

  • The first step is to compose events from native event composition and simulate capture bubbles on vDOM, collecting all the event callbacks that need to be executed to form a callback array.
  • The second step is to iterate through the callback array and trigger the callback function.

First, the extractEvents are called and the native event E is passed in. The React event system synthesizes the Synthetic event according to the possible event plug-ins. . Here we can see call EventConstructor getPooled (), a synthetic event from the event pool to get object, if the event pool is empty, the newly created a synthetic event object, which reflects the React to performance to achieve the pool of ideas.

You then pass in the Propagator, simulate capture and bubble on the vDOM, and collect all the event callbacks that need to be executed and the corresponding nodes. TraverseTwoPhase simulates both the capture and bubbling phases, and the implementation is clever enough to simply traverse the array forward and backward. For each node, we then call listenerAtPhase to retrieve the callback function that was mounted on the node at the time of the event binding and add it to the callback array.

It then iterates through all the synthesized events. When an event is processed, React calls Event.isPersistent () to see if the synthesized event needs to be persisted. If not, the synthesized event will be released. This is why when we need to read a synthesized event asynchronously, You need to execute event.persist() or React will release the event here.

Finally, this is where the callbacks are actually triggered. Take the event._dispatchlisteners array and iterate over them. And through the event. IsPropagationStopped () this step to simulate stop bubbling. React does not care if stopPropagation was called when collecting the callback array. It checks to see if bubbles need to be stopped during the trigger phase.

At this point, an event callback is triggered, in which setState etc. is executed until the call stack bounts back to the lowest interactiveUpdate to be finally flushed, the vDOM is constructed, and reconciled, and finally committed to the DOM.

So that’s the whole process of the event firing, so if you go back and watch the animation again, you’ll understand it a little bit better.

Example the Debug

Now that we’re familiar with the React event system, let’s go back to the two metaphysical questions at the beginning of this article. Why?

Example a

If you want to see the content of the title or forget the title, you can click here.

After reading this article, if you already understand the React event system, this problem should not be difficult.

  1. Because the React event listener is mounted on document, the native system is#outerThe callback to listen onCWill be printed first; The React event system simulates to capture the bubbling outputAandB; After the React event is executed, the system returns to the browser and continues to bubble to the windowD.
  2. Native system in#outerThe callback to listen onCWill be executed first; Then the native event bubbles up to the Document into the React event system and outputsAIn React event handling#innerCall thestopPropagation, the event is stopped bubbling.

Therefore, it is best not to mix the React event system with the native event system. If you do, make sure you know exactly what will happen.

Example 2

If you want to see the content of the title or forget the title, you can click here.

This is a slightly more complicated question. First, we click the Edit button and the browser triggers a click event that bubbles to document to enter the React event system. The React callback calls setState, at which point the React event system completes processing the event. React is rendered synchronously, so perform performSyncWork to change the type of the React button to “submit”. React reuses the button node (see why) and updates it to the DOM. The browser continues the click event processing, and finds that the node type=”submit”, and the form below, triggers the Submit event accordingly.

Add a key to a button; Write the two buttons separately, do not use triadic can solve the problem.

Specifically, you can take a look at the following call diagram, which should be very easy to understand. If you can’t understand anything, please leave a comment below, and I will try my best to explain it clearly.


As an extra point, “setState is asynchronous.”

The phrase “setState is asynchronous” should be heard a lot by many React developers. I remember seeing it when I first learned React. SetState ((preState)=>{}).

But that’s not entirely true. It is more accurate to say that setState is sometimes asynchronous, and setState is synchronous relative to the browser

Currently setState is asynchronous in its lifecycle and event callbacks, that is, it is collected and processed in batches. In other cases such as promises, setTimeout is done synchronously, meaning that a call to setState will render once and update it to the DOM. Click here to try it.

When the JS call stack is empty, the result must have been updated to the DOM (synchronous rendering). This is what setState means by being synchronous with the browser. As shown in the figure below

The flow chart for asynchronous rendering looks something like this. The last time I thought about this, I realized that if we were rendering asynchronously now, our example 2 would be an occasional pit 😂 because the browser would not trigger the Submit event if the setState result had not been updated to the DOM.

However, the React team has been working on the asynchronous rendering vision for two years, and with The adoption of Fiber Reconciliation and the unstable_XXX API for asynchronous rendering in Update 16, we’re sure we’ll see some performance improvements in update 17. Thanks to the React team.

conclusion

Hopefully this article will give you a sense of the React event system. “Why does React need to implement its own event system?” “And” How does the React event system work? . React implements its own event system by using event proxy, pooling, batch update, cross-browser and cross-platform compatibility, attaching event listeners to Document, constructing composite events, and internally emulating a mechanism for capturing and bubbling and triggering callback functions.

If you are not clear, find mistakes in the article, or simply exchange related questions, please leave a message below, I will do my best to reply and answer your questions. If you like my articles, please follow me and my blog, thank you.

Read More & Reference

  • Recommended!!! React events in depth w/ Kent C. Dodds, Ben Alpert, & Dan Abramov
  • Recommended!!! The React and React Native Event System Explained: A Harmonious Coexistence
  • Didact Fiber: Incremental reconciliation
  • Codebase Overview