3. React18 - 事件注册和派发
事件执行过程
html
<div id="root">
<div id="parent">
<div id="child">点击</div>
</div>
</div>
模拟过程
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible"
content="IE=edge">
<meta name="viewport"
content="width=device-width, initial-scale=1.0">
<title>了解事件捕获和冒泡过程</title>
</head>
<body>
<div id="root">
<div id="parent">
<div id="child">点击</div>
</div>
</div>
</body>
<script>
var parentBubble = () => {
console.log('父React冒泡');
}
var parentCapture = () => {
console.log('父React捕获');
}
var childBubble = () => {
console.log('子React冒泡');
}
var childCapture = () => {
console.log('子React捕获');
}
let root = document.getElementById('root');
let parent = document.getElementById('parent');
let child = document.getElementById('child');
parent.onClick = parentBubble;
parent.onClickCapture = parentCapture;
child.onClick = childBubble;
child.onClickCapture = childCapture;
root.addEventListener('click', (event) => dispatchEvent(event, true), true);
root.addEventListener('click', (event) => dispatchEvent(event, false), false);
function dispatchEvent(event, isCapture) {
let currentTarget = event.target;
//说明:从内往外寻找dom节点直到根
let paths = []; //[child,parent,root,body,html,document]
while (currentTarget) {
paths.push(currentTarget);
currentTarget = currentTarget.parentNode;
}
if (isCapture) {
//捕获阶段: 是从外往内进行依次捕获
for (let i = paths.length - 1; i >= 0; i--) {
let handler = paths[i].onClickCapture;
handler && handler();
}
} else {
//冒泡阶段: 是从内往外进行依次冒泡
for (let i = 0; i < paths.length; i++) {
let handler = paths[i].onClick;
handler && handler();
}
}
}
parent.addEventListener('click', () => {
console.log('父原生捕获');
}, true);
parent.addEventListener('click', () => {
console.log('父原生冒泡');
}, false);
child.addEventListener('click', () => {
console.log('子原生捕获');
}, true);
child.addEventListener('click', () => {
console.log('子原生冒泡');
}, false);
</script>
</html>
日志输出:
父React捕获
子React捕获
父原生捕获
子原生捕获
子原生冒泡
父原生冒泡
子React冒泡
父React冒泡
示例
jsx
import { createRoot } from "react-dom/client";
function FunctionComponent() {
return (
<h1
onClick={(event) => console.log(`ParentBubble`)}
onClickCapture={(event) => {
console.log(`ParentCapture`)
event.stopPropagation();
}}
>
<span
onClick={(event) => {
console.log(`ChildBubble`);
event.stopPropagation();
}}
onClickCapture={(event) => console.log(`ChildCapture`)}
>world</span>
</h1>
)
}
let element = <FunctionComponent />
const root = createRoot(document.getElementById("root"));
root.render(element);
事件注册和派发流程
完整流程图:https://app.diagrams.net/#G1ugEBXMgklPNhU39Vunn4mN-JhEi-hZQf#{"pageId"%3A"Sdm2yIqwJ5qRPqILZMSH"}
总结
- 创建root 真实节点开始,就将事件委托至根root节点上
- 包装捕获和冒泡阶段事件Listener至root节点上
- 通过插件事件中心来注册事件和处理事件
- 点击dom节点出发事件:根据点击的真实DOM的fiber,往上依次寻找有stateNode的fiber.props中的onClick 和onClickCapture事件,将所有listener和合成事件event 放进dispatchQueue中
- 事件处理顺序
- 捕获阶段:从外往内(先父后子)依次捕获,依次执行dispatchQueue中的listener(倒序,因为:队列是先子后父)
- 冒泡阶段:从内往外(先子后父)依次冒泡,,依次执行dispatchQueue中的listener(正序)