我使用 React 钩子创建了一个应用程序。我有一个辅助函数onClickOutsideHook(ref, callback)
,当您单击提供ref
的组件外部时,它会触发callback
React.useRef
:
export const onClickOutsideHook = (ref, callback) => {
// Hook get from https://stackoverflow.com/a/42234988/8583669
React.useEffect(() => {
const handleClickOutside = event => {
if (ref?.current && !ref.current.contains(event.target)) {
callback();
}
};
// Bind the event listener
document.addEventListener("mousedown", handleClickOutside);
return () => {
// Unbind the event listener on clean up
document.removeEventListener("mousedown", handleClickOutside);
};
}, [callback, ref]);
};
我有一个使用此帮助程序的组件Dropdown
因此当您单击其外部时它会关闭。此组件具有作为使用ReactDOM.createPortal
的子组件的Modal
组件。我用它来渲染body
中的Modal
,以便它可以覆盖所有应用程序屏幕。我的Modal
包含一个按钮,当您单击消息时会提醒消息:
function Modal() {
return ReactDOM.createPortal(
<div
style={{
position: "absolute",
top: 0,
left: 0,
height: "100%",
width: "100%",
background: "rgba(0,0,0,0.6)"
}}
>
<button onClick={() => alert("Clicked on Modal")}>Click</button>
</div>,
document.body
);
}
function Dropdown(props) {
const [isModalOpen, setIsModalOpen] = React.useState(false);
const dropdownRef = React.useRef(null);
onClickOutsideHook(dropdownRef, props.onClose);
return (
<div ref={dropdownRef}>
Click outside and I will close
<button onClick={() => setIsModalOpen(true)}>open Modal</button>
{isModalOpen ? <Modal /> : <></>}
</div>
);
}
问题是当我单击Modal
按钮触发警报时,Dropdown
之前已关闭,因为我单击了它外部(Modal
不是呈现为Dropdown
的子项,而是body
(。所以我的警报永远不会被触发。
有没有办法使用ref
将Modal
定义为Dropdown
子项,但仍使用ReactDOM.createPortal
将其呈现在body
中?
看看CodeSandbox就知道了。
就像门户文档说的那样:
尽管门户可以位于 DOM 树中的任何位置,但它的行为在其他方面都像普通的 React 子级。 ...
这包括事件冒泡。从门户内部触发的事件将 传播到包含 React 树中的祖先,即使那些 元素不是 DOM 树中的祖先。
但事实并非如此,鼠标关闭事件侦听器是在文档上添加的,而不仅仅是Dropdown组件。即便如此,冒泡仍然会发生。
这意味着,如果您向模态添加一个引用,然后在鼠标关闭事件上添加一个事件侦听器,其全部目的是停止传播,则永远不会调用handleClickOutside函数。
这似乎仍然是一种解决方法,我不知道是否有适当的检查方法。
function Modal() {
const modalRef = useRef();
useEffect(() => {
const stopPropagation = e => {
e.stopPropagation();
};
const { current: modalDom } = modalRef;
modalDom.addEventListener("mousedown", stopPropagation);
return () => {
modalDom.removeEventListener("mousedown", stopPropagation);
};
}, []);
return ReactDOM.createPortal(
<div
ref={modalRef}
style={{
position: "absolute",
top: 0,
left: 0,
height: "100%",
width: "100%",
background: "rgba(0,0,0,0.6)"
}}
>
<button onClick={() => alert("Clicked on Modal")}>Click</button>
</div>,
document.body
);
}
观看以下代码沙盒中的模态组件。
作为一种解决方法,您可以向模态添加 ID 属性,然后检查点击是否超出模态
function Modal() {
return ReactDOM.createPortal(
<div id="modalId">
<button onClick={() => alert("Clicked on Modal")}>Click</button>
</div>,
document.body
);
}
...
React.useEffect(() => {
const handleClickOutside = (event) => {
if (
ref?.current &&
!ref.current.contains(event.target) &&
document.getElementById("modalId") &&
!document.getElementById("modalId").contains(event.target) // check if click was outside your modal
) {
callback();
}
};
// Bind the event listener
document.addEventListener("mousedown", handleClickOutside);
return () => {
// Unbind the event listener on clean up
document.removeEventListener("mousedown", handleClickOutside);
};
}, [callback, ref]);
...