单击按钮我希望出现
Modal
。
Modal
组件添加了一个 eventListener
,因此当您在模态框外单击时它会关闭。在 React 18 中,点击事件触发是因为 before Modal 渲染的按钮点击?如果我改为反应 17,这不会发生。
在这里找到一个CodeSandbox。注意, 当您单击按钮时,
show
状态设置为 true。
然后Modal
组件渲染,直接调用close函数
App.js:
import { useState } from "react";
import Modal from "./Modal";
import "./styles.css";
export default function App() {
const [show, setShow] = useState(false);
const close = () => {
setShow(false);
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<button
onClick={(e) => {
setShow(true);
}}
>
Click
</button>
{show && <Modal close={close} />}
</div>
);
}
Modal.js
import "./styles.css";
import { useRef, useEffect } from "react";
export default function Modal({ close }) {
const ref = useRef(null);
useEffect(() => {
const handleOutsideClick = (e) => {
if (!ref?.current?.contains(e.target)) {
console.log("This one gets called because of the button click", e);
close();
}
};
document.addEventListener("click", handleOutsideClick, false);
return () => {
document.removeEventListener("click", handleOutsideClick, false);
};
}, [close]);
return (
<div ref={ref} className="Modal">
<h1>I'm a Modal!</h1>
</div>
);
}
您可以调用 event.stopPropagation 来防止多个点击处理程序捕获相同的点击事件。
onClick={(e) => {
e.stopPropagation();
setShow(true);
}}
我不知道为什么这会在 React 17 和 18 之间有所不同。React 使用自己的“合成事件”,两个版本之间的事件传播/冒泡方式可能发生了变化。
它可能与 React 18 中所谓的“自动批处理”有关。 https://github.com/reactwg/react-18/discussions/21
在您的示例中,Modal 组件使用
document.addEventlistener()
的本机事件处理。似乎 React 18 处理应用程序内部的点击,触发状态更改和重新渲染,安装Modal
组件,运行useEffect()
挂钩,并在点击事件传播到窗口之前创建新的事件侦听器节点。在 React 17 中,事件可能会在重新渲染发生之前完成传播。
Dan Abramov 在此评论中给出了一些建议(https://github.com/facebook/react/issues/24657#issuecomment-1150119055)。
建议1是:在useEffect中,延迟addEventListener的调用,设置一个等待时间为0的setTimeout。例子:
useEffect(() => {
const handleOutsideClick = (e) => {
if (!ref?.current?.contains(e.target)) {
console.log("This one gets called because of the button click", e);
close();
}
};
const timeoutId = setTimeout(() => {
document.addEventListener("click", handleOutsideClick, false);
}, 0);
return () => {
clearTimeout(timeoutId);
document.removeEventListener("click", handleOutsideClick, false);
};
});
建议 2 是捕获当前事件并忽略 modal 的 useEffect 中的那个事件。 @yousoumar 在this answer 和https://github.com/microsoft/fluentui/pull/18323
中提供的示例问题的不同解决方案涉及新的
startTransition
API:https://codesandbox.io/s/gracious-benz-0ey6ol?file=/src/App.js:340-388
onClick={(e) => {
startTransition(() => {
setShow(true);
})
}}
这个新的 API (doc) 肯定有帮助,但我不确定这是一个合法的解决方案,文档对此也不是很清楚。如果来自 React 的人可以发表评论就好了——这是一种滥用吗?
您可以在
if
中更改您的handleOutsideClick
声明,如下所示。你的不起作用,因为用于打开 button
的 Modal
,因为它不在 Modal
内,通过了这个
if(!ref?.current?.contains(e.target))
你有。
这是您的 CodeSandbox 的工作叉。我正在将按钮的
ref
传递给Modal
。这是我改变的:
const handleOutsideClick = (e) => {
if (!ref?.current?.contains(e.target) && !openButtonRef?.current?.contains(e.target)) {
console.log("This one gets called because of the button click", e);
close();
}
};