18
头图

Hello everyone, I'm Casson.

The event system code React is large for the following reasons:

  • Need to smooth out the differences between different browsers
  • Binds with internal priority mechanism
  • All browser events need to be considered

But if you take a closer look, you will find that the core of the event system has only two modules:

  • SyntheticEvent
  • Simulation implementation of event propagation mechanism

This article will implement these two modules with 60 lines of code, allowing you to quickly understand the principle of the React

Online DEMO address

Welcome to join human high-quality front-end framework group , with flying

Demo effect

For the following JSX :

const jsx = (
  <section onClick={(e) => console.log("click section")}>
    <h3>你好</h3>
    <button
      onClick={(e) => {
        // e.stopPropagation();
        console.log("click button");
      }}
    >
      点击
    </button>
  </section>
);

Render in browser:

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

Clicking the button will print in sequence:

click button
click section

If you button the click callback of e.stopPropagation() , it will print after clicking:

click button

Our goal is to JSX in onClick with ONCLICK , but the effect after clicking is unchanged.

In other words, we will be based on React made an event system, writing rules his event name is shaped like onXXX of all uppercase form.

Implement SyntheticEvent

First, let's implement SyntheticEvent (synthetic event).

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

SyntheticEvent exists to smooth event objects SyntheticEvent does not provide polyfill for browsers that do not support an event (as this would significantly increase the size of ReactDOM

Our implementation is simple:

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

Receive native event object and return a wrapper object. native event object is stored in the nativeEvent property.

At the same time, the stopPropagation method is implemented.

The actual SyntheticEvent will contain many more properties and methods, simplified here for demonstration purposes

Implement event propagation mechanism

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

  1. event type on the root node. All descendant nodes that trigger this type of event will eventually delegate to the event callback root node for processing.
  2. Find the DOM node that triggered the event and find its corresponding FiberNode (ie, virtual DOM node)
  3. Collect all registered from the current FiberNode to the root FiberNode . This event corresponds to the callback
  4. Reverse traversal and execute all collected callbacks (simulate the implementation of the capture phase)
  5. Traverse forward and execute all collected callbacks (simulate the implementation of the bubbling phase)

First, implement the first step:

// 步骤1
const addEvent = (container, type) => {
  container.addEventListener(type, (e) => {
    // dispatchEvent是需要实现的“根节点的事件回调”
    dispatchEvent(e, type.toUpperCase(), container);
  });
};

click callback at entry:

const root = document.querySelector("#root");
ReactDOM.render(jsx, root);
// 增加如下代码
addEvent(root, "click");

Next to realize root event callbacks :

const dispatchEvent = (e, type) => {
  // 包装合成事件
  const se = new SyntheticEvent(e);
  const ele = e.target;
  
  // 比较hack的方法,通过DOM节点找到对应的FiberNode
  let fiber;
  for (let prop in ele) {
    if (prop.toLowerCase().includes("fiber")) {
      fiber = ele[prop];
    }
  }
  
  // 第三步:收集路径中“该事件的所有回调函数”
  const paths = collectPaths(type, fiber);
  
  // 第四步:捕获阶段的实现
  triggerEventFlow(paths, type + "CAPTURE", se);
  
  // 第五步:冒泡阶段的实现
  if (!se._stopPropagation) {
    triggerEventFlow(paths.reverse(), type, se);
  }
};

The next collection route all callbacks the event .

Event callback function in collection path

The idea of implementation is: FiberNode until the root FiberNode . Collect the corresponding event callback FiberNode.memoizedProps attribute during the collection traversal process:

const collectPaths = (type, begin) => {
  const paths = [];
  
  // 不是根FiberNode的话,就一直向上遍历
  while (begin.tag !== 3) {
    const { memoizedProps, tag } = begin;
    
    // 5代表DOM节点对应FiberNode
    if (tag === 5) {
      const eventName = ("on" + type).toUpperCase();
      
      // 如果包含对应事件回调,保存在paths中
      if (memoizedProps && Object.keys(memoizedProps).includes(eventName)) {
        const pathNode = {};
        pathNode[type.toUpperCase()] = memoizedProps[eventName];
        paths.push(pathNode);
      }
    }
    begin = begin.return;
  }
  
  return paths;
};

The resulting paths structure is similar to the following:

Implementation of the capture phase

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

[目标事件回调, 某个祖先事件回调, 某个更久远的祖先回调 ...]

To simulate the capture phase of 161f4a6b84a647, it is necessary to traverse the array from back to front and execute the callback.

The traversal method is as follows:

const triggerEventFlow = (paths, type, se) => {
  // 从后向前遍历
  for (let i = paths.length; i--; ) {
    const pathNode = paths[i];
    const callback = pathNode[type];
    
    if (callback) {
      // 存在回调函数,传入合成事件,执行
      callback.call(null, se);
    }
    if (se._stopPropagation) {
      // 如果执行了se.stopPropagation(),取消接下来的遍历
      break;
    }
  }
};

Note that we SyntheticEvent implemented in stopPropagation method, after a call to prevent traversal continues.

Implementation of the bubbling phase

With the implementation experience of the capture phase , the bubbling phase is easy to implement, just paths and traverse it again.

Summarize

React event system consists of two parts:

  • SyntheticEvent
  • event propagation mechanism

event propagation mechanism is implemented in 5 steps.

Overall, it's that simple.


卡颂
3.1k 声望16.7k 粉丝