Hello, everyone. I am Karsong.

The React event system has a large amount of code for the following reasons:

  • You need to smooth out differences between browsers

  • Bind to the internal priority mechanism

  • All browser events need to be considered

But if you pull the strings, there are only two modules at the core of the event system:

  • SyntheticEvent

  • Simulate the event propagation mechanism implemented

This article uses 60 lines of code to implement both modules, giving you a quick overview of how the React event system works.

Online DEMO Address

Welcome to join the Human high quality front-end framework research group, Band fly

The effect of the Demo

For the following JSX:

const jsx = (
  <section onClick={(e)= > console.log("click section")}>
    <h3>hello</h3>
    <button
      onClick={(e)= >{ // e.stopPropagation(); console.log("click button"); }} > click</button>
  </section>
);
Copy the code

Render in a browser:

const root = document.querySelector("#root");
ReactDOM.render(jsx, root);
Copy the code

Click the button and it will print:

click button
click section
Copy the code

If I add e.topPropagation () to button’s click callback, it will print:

click button
Copy the code

Our goal is to replace onClick with onClick in JSX, but the effect remains the same.

In other words, we will create our own event system based on React, whose event names are written in all uppercase forms like ONXXX.

Implement SyntheticEvent

First, let’s implement a SyntheticEvent.

SyntheticEvent is a wrapper around the browser’s native event object. Compatible with all browsers and has the same API as browser native events such as stopPropagation() and preventDefault().

Syntheticevents exist to smooth out differences between browser event objects, but for browsers that do not support an event, syntheticEvents do not provide polyfill (because they significantly increase the size of the ReactDOM).

Our implementation is simple:

class SyntheticEvent {
  constructor(e) {
    this.nativeEvent = e;
  }
  stopPropagation() {
    this._stopPropagation = true;
    if (this.nativeEvent.stopPropagation) {
      this.nativeEvent.stopPropagation(); }}}Copy the code

Receives the native event object and returns a wrapper object. NativeEvent objects are stored in the nativeEvent property.

At the same time, the stopPropagation method is implemented.

The actual SyntheticEvent would have more properties and methods, simplified here for demonstration purposes

Implement event propagation mechanisms

The implementation steps of the event propagation mechanism are as follows:

  1. The root node binds the event callback corresponding to the event type, and all descendant nodes that trigger the event type are ultimately delegated to the root node’s event callback processing.

  2. Find the DOM node that triggered the event and find its FiberNode (virtual DOM node)

  3. Collect all registered callback corresponding to this event from the current FiberNode to root FiberNode

  4. Reverse traverse and execute all collected callbacks once (emulating the capture phase implementation)

  5. Forward traversal and execution of all collected callbacks (simulating the bubbling phase implementation)

First, implement the first step:

/ / step 1
const addEvent = (container, type) = > {
  container.addEventListener(type, (e) = > {
    // dispatchEvent is the "root node event callback" that needs to be implemented.
    dispatchEvent(e, type.toUpperCase(), container);
  });
};
Copy the code

Register for callbacks at the entry:

const root = document.querySelector("#root");
ReactDOM.render(jsx, root);
// Add the following code
addEvent(root, "click");
Copy the code

Next implement the event callback for the root node:

const dispatchEvent = (e, type) = > {
  // Wrap the compositing event
  const se = new SyntheticEvent(e);
  const ele = e.target;
  
  // Find the corresponding FiberNode through DOM node
  let fiber;
  for (let prop in ele) {
    if (prop.toLowerCase().includes("fiber")) { fiber = ele[prop]; }}// Step 3: Collect "all callbacks for this event" in the path
  const paths = collectPaths(type, fiber);
  
  // Step 4: Implement the capture phase
  triggerEventFlow(paths, type + "CAPTURE", se);
  
  // Step 5: Implement the bubble phase
  if (!se._stopPropagation) {
    triggerEventFlow(paths.reverse(), type, se);
  }
};
Copy the code

Next, collect all callbacks for the event in the path.

Collect the event callback function in the path

The idea is to traverse the current FiberNode all the way up to the root FiberNode. Collect the corresponding event callbacks saved in the FiberNode. MemoizedProps property during the traverse:

const collectPaths = (type, begin) = > {
  const paths = [];
  
  // If it is not a FiberNode, go up
  while(begin.tag ! = =3) {
    const { memoizedProps, tag } = begin;
    
    // 5 represents FiberNode corresponding to DOM node
    if (tag === 5) {
      const eventName = ("on" + type).toUpperCase();
      
      // If it contains the corresponding event callback, save it in Paths
      if (memoizedProps && Object.keys(memoizedProps).includes(eventName)) {
        const pathNode = {};
        pathNode[type.toUpperCase()] = memoizedProps[eventName];
        paths.push(pathNode);
      }
    }
    begin = begin.return;
  }
  
  return paths;
};
Copy the code

The resulting Paths structure looks like this:

Implementation of the capture phase

Since we are traversing up from the target FiberNode, the order of the callbacks collected is:

Target event callback, some ancestor event callback, some older ancestor callback...Copy the code

To simulate the implementation of the capture phase, you need to iterate through the array from back to front and perform a callback.

The traversal method is as follows:

const triggerEventFlow = (paths, type, se) = > {
  // traverse from back to front
  for (let i = paths.length; i--; ) {
    const pathNode = paths[i];
    const callback = pathNode[type];
    
    if (callback) {
      // there is a callback function, pass in the synthesis event, execute
      callback.call(null, se);
    }
    if (se._stopPropagation) {
      // If se.stopPropagation() is executed, cancel the following traversal
      break; }}};Copy the code

Note that the stopPropagation method we implemented in SyntheticEvent prevents the traversal from continuing when called.

Implementation of the bubbling phase

With the experience of implementing the capture phase, the bubbling phase is easy to implement by going back through the Paths again.

conclusion

The React event system has two core parts:

  • SyntheticEvent

  • Event propagation mechanism

The event propagation mechanism is implemented in five steps.

In general, it’s that simple.