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 sectionIf you button the click callback of e.stopPropagation() , it will print after clicking:
click buttonOur 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:
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.- Find the DOM node that triggered the event and find its corresponding
FiberNode(ie, virtual DOM node) - Collect all registered from the current
FiberNodeto the rootFiberNode. This event corresponds to the callback - Reverse traversal and execute all collected callbacks (simulate the implementation of the capture phase)
- 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.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用。你还可以使用@来通知其他用户。