React中阻止事件冒泡的问题详析

最近在研究react、redux等,网上找了很久都没有完整的答案,索性自己整理下,这篇文章就来给大家介绍了关于React阻止事件冒泡的相关内容,下面话不多说了,来一起看看详细的介绍吧

在正式开始前,先来看看 JS 中事件的触发与事件处理器的执行。

JS 中事件的监听与处理

事件捕获与冒泡

DOM 事件会先后经历 捕获 与 冒泡 两个阶段。捕获即事件沿着 DOM 树由上往下传递,到达触发事件的元素后,开始由下往上冒泡。

IE9 及之前的版本只支持冒泡

|  A
 -----------------|--|-----------------
 | Parent         |  |                |
 |   -------------|--|-----------     |
 |   |Children    V  |          |     |
 |   ----------------------------     |
 |                                    |
 --------------------------------------

事件处理器

默认情况下,事件处理器是在事件的冒泡阶段执行,无论是直接设置元素的 onclick 属性还是通过 EventTarget.addEventListener() 来绑定,后者在没有设置 useCapture 参数为 true 的情况下。

考察下面的示例:

<button>CLICK ME</button> <script> document.addEventListener("click", function(event) { console.log("document clicked"); }); function btnClickHandler(event) { console.log("btn clicked"); } </script>

输出:

btn clicked
document clicked

阻止事件的冒泡

通过调用事件身上的 stopPropagation() 可阻止事件冒泡,这样可实现只我们想要的元素处理该事件,而其他元素接收不到。

<button>CLICK ME</button> <script> document.addEventListener( "click", function(event) { console.log("document clicked"); }, false ); function btnClickHandler(event) { event.stopPropagation(); console.log("btn clicked"); } </script>

输出:

btn clicked

一个阻止冒泡的应用场景

常见的弹窗组件中,点击弹窗区域之外关闭弹窗的功能,可通过阻止事件冒泡来方便地实现,而不用这种方式的话,会引入复杂的判断当前点击坐标是否在弹窗之外的复杂逻辑。

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>; }

输出:

btn clicked
document clicked

document 上的事件处理器正常执行了,并没有因为我们在按钮里面调用 event.stopPropagation() 而阻止。

那么问题出在哪?

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"); });

下面是剧透环节,如果你懒得自己实验的话。

点击按钮后的输出:

body
document:bedore react mount
react:button
raect:container
document:after react mount
document within react
window

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/e0674b3d3f7c102b62e74db7beff2252.html