目标: 我想在 React 中实现一个弹出框(模态)。 当用户单击按钮时,弹出窗口将打开。 当用户在弹出窗口外部单击时关闭弹出窗口。
逻辑: 使用状态有条件地渲染弹出框组件。 在popover组件中,添加事件监听器,监听文档的点击事件。 如果用户单击文档,它会将状态更改为 false 以不渲染弹出窗口组件。
问题: 如果我单击按钮打开弹出窗口,则会触发事件侦听器。 这样弹出窗口就会自动关闭。
我想知道为什么会发生这种情况。
import { useState } from "react";
import Popover from "./Popover";
const TaskButton = () => {
const [isPopoverShown, setIsPopoverShown] = useState(false);
const popoverOpenHandler = () => {
setIsPopoverShown(true);
};
const popoverCloseHandler = () => {
setIsPopoverShown(false);
};
return (
<div>
<button
onClick={popoverOpenHandler}
>
button
</button>
{isPopoverShown && (
<Popover isOpen={isPopoverShown} onClose={popoverCloseHandler}>
hello
</Popover>
)}
</div>
);
};
export default TaskButton;
import React, { useEffect, useRef } from "react";
const Popover =({ onClose, children }) => {
const popoverRef = useRef(null);
useEffect(() => {
const pageClickEvent = (event) => {
if (popoverRef.current !== event.target) {
onClose();
}
};
document.addEventListener("click", pageClickEvent);
return () => {
document.removeEventListener("click", pageClickEvent);
};
}, [onClose]);
return <div ref={popoverRef}>{children}</div>;
}
export default Popover;
我的想法:
我认为问题出在事件冒泡和捕获上。
document.addEventListener("mousedown", pageClickEvent);
document.addEventListener("click", pageClickEvent, true);
因为当我更改其中之一的代码时,它会按照我的预期工作。
但我想知道为什么如果我将事件“click”更改为“mousedown”(或向上)它会起作用。 或者如果我将捕获选项更改为 true。
我认为只有当组件安装在 DOM 上时才会激活事件监听器。 因此,不应通过单击按钮打开组件本身来触发事件监听器。
由于事件传播,您正在观察此行为。
当您单击按钮打开弹出窗口时,单击事件首先由按钮本身捕获,然后向上冒泡到文档级别。由于您的事件侦听器位于文档上并且正在侦听任何单击事件,因此它会在按钮的单击事件后立即触发。
为了防止这种行为,您需要停止将按钮的单击事件传播到文档级别。您可以使用 e.stopPropagation() 方法来实现此目的。以下是修改 popoverOpenHandler 的方法:
const popoverOpenHandler = (e) => {
e.stopPropagation();
setIsPopoverShown(true);
};
通过在
popoverOpenHandler
内调用 e.stopPropagation(),可以防止单击事件传播到按钮本身之外。这意味着按钮单击不会触发文档的单击事件侦听器,并且当您尝试打开它时,您的弹出窗口不会立即关闭。
一些可能有用的其他资源:https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation
我希望这有帮助。