此问题涉及到 React 在开发过程中故意重新安装组件以查找 bug 的事实。
我知道我可以关闭严格模式,但 React 不推荐这样做。我只是在寻找推荐的方法来解决这个问题。
我正在尝试构建一个打开模态组件,当单击关闭按钮或用户在模态内容之外单击时,该组件会关闭。
因此,为了实现模式的外部单击关闭,我使用了 useEffect 挂钩,我希望其回调函数仅在 isModalPopedUp 状态更改时运行,并且不会在组件的初始渲染时运行。为此,我使用了一个自定义钩子:
import { useRef } from "react";
import { useEffect } from "react";
// This custom hook sets if a useEffect callback runs at initial render or not
export default function useDidMountEffect(argObject = {}) {
/*
argObject = {
runOnInitialRender: boolean,
callback: () => void,
dependencies: any[] // array of dependencies for useEffect
}
*/
const didMountRef = useRef(argObject.runOnInitialRender);
// useEffect to run
useEffect(() => {
if (didMountRef.current) {
// callback will run on first render if argObject.runOnInitialRender is true
argObject.callback();
} else {
// callback will now run on dependencies change
didMountRef.current = true;
}
}, argObject.dependencies); // only run if dependencies change
}
对于模态组件,我将其分为两个组件,处理状态逻辑的父模态:
import { useEffect, useState } from "react";
import Modal from "./modal";
import useDidMountEffect from "./useDidMountHook";
import "./modal.css";
export default function ModalParent() {
const [isModalPopedUp, setIsModalPopedUp] = useState(false);
function handleToggleModalPopup() {
setIsModalPopedUp((prevState) => !prevState);
}
function handleOnClose() {
setIsModalPopedUp(false);
}
function handleOutsideModalClick(event) {
!event.target.classList.contains("modal-content") && handleOnClose();
}
// add event listener for outside modal click using the useDidMountEffect hook
useDidMountEffect({
runOnInitialRender: false,
callback: () => {
// add event listener when modal is shown
if (isModalPopedUp) {
document.addEventListener("click", handleOutsideModalClick);
} else {
// remove event listener when modal is closed
document.removeEventListener("click", handleOutsideModalClick);
}
// return a cleanup function that removes the event listener when component unmounts
return () => {
document.removeEventListener("click", handleOutsideModalClick);
};
},
dependencies: [isModalPopedUp], // only re-run the effect when isModalPopedUp changes
});
return (
<div>
<button
onClick={() => {
handleToggleModalPopup();
}}>
Open Modal Popup
</button>
{isModalPopedUp && (
<Modal
header={<h1>Customised Header</h1>}
footer={<h1>Customised Footer</h1>}
onClose={handleOnClose}
body={<div>Customised Body</div>}
/>
)}
</div>
);
}
以及主要模态组件:
export default function Modal({ id, header, body, footer, onClose }) {
return (
<div id={id || "modal"} className="modal">
<div className="modal-content">
<div className="header">
<span onClick={onClose} className="close-modal-icon">
× {/* an X icon */}
</span>
<div>{header ? header : "Header"}</div>
</div>
<div className="body">
{body ? (
body
) : (
<div>
<p>This is our Modal body</p>
</div>
)}
</div>
<div className="footer">
{footer ? footer : <h2>footer</h2>}
</div>
</div>
</div>
);
}
所以,问题是 React 在初始渲染后重新挂载父组件,回调 useDidMountEffect 立即将 click 事件监听器添加到
document
元素,而不会通过“open”将 isModalPopedUp 状态更改为 true模态”按钮单击。
因此,当单击“打开模态”按钮时,isModalPopedUp 会切换为 true,但由于单击事件侦听器过早添加到 document,因此立即更改为 false。所以,最终导致无法通过点击“打开模态”按钮来打开模态。
React.dev 通过使用 useEffect 回调返回的清理函数来撤消重新挂载的更改,提供了一个简单的修复方法:
通常,答案是实现清理功能。 清理函数应该停止或撤消 Effect 正在执行的任何操作。经验法则是,用户不应该能够区分运行一次的效果(如在生产中)和设置→清理→设置序列(如您在开发中看到的)。
我的清理功能从
document
元素中删除了单击事件侦听器,但仍然无法解决问题。
对于模态的样式,我正在使用:
/** @format */
.modal {
position: fixed;
z-index: 1;
padding-top: 2rem;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: hidden;
background-color: #b19b9b;
color: black;
overflow: auto;
padding-bottom: 2rem;
}
.modal-content {
position: relative;
background-color: #fefefe;
margin: auto;
padding: 0;
border: 1px solid red;
width: 80%;
animation-name: animateModal;
animation-duration: 0.5s;
animation-timing-function: ease-in-out;
}
.close-modal-icon {
cursor: pointer;
font-size: 40px;
position: absolute;
top: 0.5rem;
right: 0.5rem;
font-weight: bold;
}
.header {
padding: 4px 10px;
background-color: #5cb85c;
color: white;
}
.body {
padding: 2px 16px;
height: 200px;
}
.footer {
padding: 4px 16px;
background-color: #5cb85c;
color: white;
}
@keyframes animateModal {
from {
top: -200px;
opacity: 0;
}
to {
top: 0;
opacity: 1;
}
}
以下内容将帮助您设置处理模态框之外的点击所需的内容。 const modalRef = useRef(null); const [isModalOpen, setIsModalOpen] = useState(false);
const handleClickOutside = (e: MouseEvent) => {
if (modalRef.current && !commandBarRef.current.contains(e.target as Node)){
setIsModalOpen(false);
}
};
除此之外,您还需要设置引用,并在 useEffect 中设置如下内容:
useEffect(() => {
if(isModalOpen){
window.addEventListener('mousedown', handleClickOutside
}
}, [isModalOpen]);