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:
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
FiberNode
to 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) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。