问题描述
我创建了简单但有效的下拉菜单。在此示例中,菜单仅包含文本“菜单”且不包含样式。
单击“打开菜单”按钮时,应用程序将显示包含菜单的div
,并将eventListener添加到文档单击事件中。添加eventListener并用户单击文档中的任意位置时,函数将检查菜单内是否发生了单击,如果是,则不执行任何操作。如果click在菜单外,它将删除eventHandler并关闭菜单。
此方法有什么问题吗?这样做的主要问题是,当我单击页面上的任何链接时如果打开菜单,则会收到此讨厌的反应警告:
index.js:1警告:无法在已卸载的组件上执行React状态更新。这是空操作,但它表明应用程序中发生内存泄漏。要修复,请取消使用useEffect清理功能中的所有订阅和异步任务。
我已经添加了useEffect
清理功能,以便在打开菜单时删除eventHandler,但这无济于事,我仍然收到相同的错误消息。
你能指出我做错了什么吗?
const DropDown = () => {
const [open,setOpen] = useState(false);
const ref = useRef<HTMLDivElement>(null);
const closeMenu = (event: MouseEvent) => {
if (event.target && event.target instanceof HTMLElement && ref.current) {
if (ref.current.contains(event.target)) return;
}
document.removeEventListener('click',closeMenu);
setOpen(false);
};
const toggleMenu = () => {
if (!open) {
document.addEventListener('click',closeMenu);
setOpen(true);
} else {
document.removeEventListener('click',closeMenu);
setOpen(false);
}
};
useEffect(() => {
return () => {
if (open) {
document.removeEventListener('click',closeMenu);
setOpen(false);
}
};
},[closeMenu]);
return (
<>
<button onClick={toggleMenu}>open menu</button>
{open && <div ref={ref}>menu</div>}
</>
);
};
export default DropDown;
解决方法
我认为问题是由setOpen(false);
钩中的useEffect
引起的:卸载组件时会调用清除函数,因此设置状态是没有意义的。出于同样的原因,检查菜单是否打开是多余的。如果菜单已卸载,则可以删除事件处理程序。
尝试:
const DropDown = () => {
const [open,setOpen] = useState(false);
const ref = useRef < HTMLDivElement > (null);
/*
*"useCallback()" hook will avoid unnecessary re-creating
* the function at each re-render
*/
const closeMenu = useCallback((event: MouseEvent) => {
if (event.target && event.target instanceof HTMLElement && ref.current) {
if (ref.current.contains(event.target)) return;
}
/*
* You do not need to remove the event handler here
* you create it when the component is mounted and
* remove it when it is unmounted via the "useEffect()" hook
*/
//document.removeEventListener('click',closeMenu);
setOpen(false);
},[]);
const toggleMenu = () => {
/*
* This is not necessary,you can do it in one line.
* Actually,you can entirely remove the function and
* call 'setOpen(!open)' directly from the button:
* <button onClick={() => {setOpen(!open)}}>
*/
//if (!open) {
// document.addEventListener('click',closeMenu);
// setOpen(true);
//} else {
// document.removeEventListener('click',closeMenu);
// setOpen(false);
//}
setOpen(!open);
};
useEffect(() => {
document.addEventListener('click',closeMenu);
return () => {
document.removeEventListener('click',closeMenu);
};
},[closeMenu]);
return ( <
>
<
button onClick = {
toggleMenu
} > open menu < /button> {
open && < div ref = {
ref
} > menu < /div>} <
/>
);
};
export default DropDown;