如何在使用 ReactDOM.createPortal() 的子孩子上处理 ref.current.contains()



我使用 React 钩子创建了一个应用程序。我有一个辅助函数onClickOutsideHook(ref, callback),当您单击提供ref的组件外部时,它会触发callbackReact.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(。所以我的警报永远不会被触发。

有没有办法使用refModal定义为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]);
...

相关内容

  • 没有找到相关文章

最新更新