在正式开始前,先来看看 JS 中事件的触发与事件处理器的执行。 JS 中事件的监听与处理事件捕获与冒泡DOM 事件会先后经历 捕获 与 冒泡 两个阶段。捕获即事件沿着 DOM 树由上往下传递,到达触发事件的元素后,开始由下往上冒泡。
事件处理器默认情况下,事件处理器是在事件的冒泡阶段执行,无论是直接设置元素的 考察下面的示例: <button onclick="btnClickHandler(event)">CLICK ME</button>
<script>
document.addEventListener("click", function(event) {
console.log("document clicked");
});
function btnClickHandler(event) {
console.log("btn clicked");
}
</script>
输出:
阻止事件的冒泡通过调用事件身上的 <button onclick="btnClickHandler(event)">CLICK ME</button>
<script>
document.addEventListener(
"click",
function(event) {
console.log("document clicked");
},
false
);
function btnClickHandler(event) {
event.stopPropagation();
console.log("btn clicked");
}
</script>
输出:
一个阻止冒泡的应用场景常见的弹窗组件中,点击弹窗区域之外关闭弹窗的功能,可通过阻止事件冒泡来方便地实现,而不用这种方式的话,会引入复杂的判断当前点击坐标是否在弹窗之外的复杂逻辑。 document.addEventListener("click", () => {
// close dialog
});
dialogElement.addEventListener("click", event => {
event.stopPropagation();
});
但如果你尝试在 React 中实现上面的逻辑,一开始的尝试会让你怀疑人生。 React 下事件执行的问题了解了 JS 中事件的基础,一切都没什么难的。在引入 React 后,,事情开始起变化。将上面阻止冒泡的逻辑在 React 里实现一下,代码大概像这样: function App() {
useEffect(() => {
document.addEventListener("click", documentClickHandler);
return () => {
document.removeEventListener("click", documentClickHandler);
};
}, []);
function documentClickHandler() {
console.log("document clicked");
}
function btnClickHandler(event) {
event.stopPropagation();
console.log("btn clicked");
}
return <button onClick={btnClickHandler}>CLICK ME</button>;
}
输出:
document 上的事件处理器正常执行了,并没有因为我们在按钮里面调用 那么问题出在哪? React 中事件处理的原理考虑下面的示例代码并思考点击按钮后的输出。 import React, { useEffect } from "react";
import ReactDOM from "react-dom";
window.addEventListener("click", event => {
console.log("window");
});
document.addEventListener("click", event => {
console.log("document:bedore react mount");
});
document.body.addEventListener("click", event => {
console.log("body");
});
function App() {
function documentHandler() {
console.log("document within react");
}
useEffect(() => {
document.addEventListener("click", documentHandler);
return () => {
document.removeEventListener("click", documentHandler);
};
}, []);
return (
<div
onClick={() => {
console.log("raect:container");
}}
>
<button
onClick={event => {
console.log("react:button");
}}
>
CLICK ME
</button>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
document.addEventListener("click", event => {
console.log("document:after react mount");
});
现在对代码做一些变动,在 body 的事件处理器中把冒泡阻止,再思考其输出。 document.body.addEventListener("click", event => {
+ event.stopPropagation();
console.log("body");
});
下面是剧透环节,如果你懒得自己实验的话。 点击按钮后的输出:
bdoy 上阻止冒泡后,你可能会觉得,既然 body 是按钮及按钮容器的父级,那么按钮及容器的事件会正常执行,事件到达 body 后, body 的事件处理器执行,然后就结束了。 document 上的事件处理器一个也不执行。 事实上,按钮及按钮容器上的事件处理器也没执行,只有 body 执行了。 输出:
通过下面的分析,你能够完全理解上面的结果。 SyntheticEventReact 有自身的一套事件系统,叫作 SyntheticEvent。叫什么不重要,实现上,其实就是通过在 document 上注册事件代理了组件树中所有的事件(facebook/react#4335),并且它监听的是 document 冒泡阶段。你完全可以忽略掉 SyntheticEvent 这个名词,如果觉得它有点让事情变得高大上或者增加了一些神秘的话。 除了事件系统,它有自身的一套,另外还需要理解的是,界面上展示的 DOM 与我们代码中的 DOM 组件,也是两样东西,需要在概念上区分开来。 所以,当你在页面上点击按钮,事件开始在原生 DOM 上走捕获冒泡流程。React 监听的是 document 上的冒泡阶段。事件冒泡到 document 后,React 将事件再派发到组件树中,然后事件开始在组件树 DOM 中走捕获冒泡流程。 现在来尝试理解一下输出结果:
理解 React 是通过监听 document 冒泡阶段来代理组件中的事件,这点很重要。同时,区分原生 DOM 与 React 组件,也很重要。并且,React 组件上的事件处理器接收到的 紧接着的代码的改动中,我们在 body 上阻止了事件冒泡,这样事件在 body 就结束了,没有到达 document,那么 React 的事件就不会被触发,所以 React 组件树中,按钮及容器就没什么反应。如果没理解到这点,光看表象还以为是 bug。 进而可以理解,如果在 document.addEventListener("click", event => {
+ event.stopImmediatePropagation();
console.log("document:bedore react mount");
});
输出:
所以,虽然都是监听 document 上的点击事件,但 解答前面按钮未能阻止冒泡的问题如果你已经忘了,这是相应的代码及输出。到这里,已经可以解答为什么 React 组件中 button 的事件处理器中调用 问题的解决要解决这个问题,这里有不止一种方法。 用
|
React 中阻止事件冒泡的问题
Posted 10ve
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了React 中阻止事件冒泡的问题相关的知识,希望对你有一定的参考价值。
以上是关于React 中阻止事件冒泡的问题的主要内容,如果未能解决你的问题,请参考以下文章
深入React事件系统(React点击空白部分隐藏弹出层;React阻止事件冒泡失效)