Reactjs 为什么在useEffect回调之后立即运行React useEffect清理函数,然后再也不会运行了?
我有一个带有组件的小React应用程序,该组件有一个打开小菜单的按钮,我希望当用户单击组件外的任何位置时,它能关闭菜单Reactjs 为什么在useEffect回调之后立即运行React useEffect清理函数,然后再也不会运行了?,reactjs,react-hooks,use-effect,Reactjs,React Hooks,Use Effect,我有一个带有组件的小React应用程序,该组件有一个打开小菜单的按钮,我希望当用户单击组件外的任何位置时,它能关闭菜单 函数setupDocumentClickEffect(onClick=(()=>{})){ console.log('adddoc click'); document.addEventListener('click',onClick); return()=>{//Clean-up console.log('删除文档单击'); document.removeEventListen
函数setupDocumentClickEffect(onClick=(()=>{})){
console.log('adddoc click');
document.addEventListener('click',onClick);
return()=>{//Clean-up
console.log('删除文档单击');
document.removeEventListener('click',onClick');
};
}
函数MyComponent(){
const[open,setOpen]=useState(false);
//设置一种效果,如果单击组件外部的文档,该效果将关闭组件
如果(打开){
const close=()=>{setOpen(false);};
使用效果(设置文档单击效果(关闭),[打开];
}
常量stoptrop=(event)=>{event.stoptropagation();};
consttoggleopen=()=>{setOpen(!open);};
// ...
//返回一个html接口,如果单击组件本身,该接口将调用Stoptrop,
//或在单击特定按钮时切换打开。
}
当组件第一次打开时,它将立即运行回调和清理。控制台将显示:添加文档单击
和删除文档单击
。如果组件已关闭,然后重新打开,则只需单击添加文档,即可按预期操作,而不运行清理。。。但这样一来,清理工作就再也不会进行了
我想我必须重新构造它,这样它就不会使用
if(open)
,而是每次运行useffect
?但是我不知道为什么清理会这样运行。我怀疑这是因为您正在调用setupDocumentClickEffect(close)
的内部useffect()
。使用延迟调用,如useffect(()=>setupDocumentClickEffect(close),[])
就是您想要的
它可能不会破坏useEffect钩子,但最好将
if(open)
合并到setupDocumentClickEffect()
中,而不是将钩子包装在其中。这里有一些错误。useffect
的第一个参数应该是一个回调函数,您将从setupDocumentClickEffect
返回该函数,这意味着setupDocumentClickEffect(close)
的返回值将在装载时立即运行,不再运行
它应该更像这样:
useffect(()=>{
如果(!打开){
返回;
}
console.log('adddoc click');
document.addEventListener(“单击”,关闭);
return()=>{//Clean-up
console.log('删除文档单击');
文档。删除EventListener(“单击”,关闭);
};
},[开放];
这里的另一个错误是你违反了钩子的规则:
您不应该在条件中定义钩子
编辑
要详细说明当前的useffect
中发生了什么,基本上可以归结为您是否编写了以下内容:
if(打开){
const close=()=>{setOpen(false);};
console.log('adddoc click');
document.addEventListener(“单击”,关闭);
useffect(()=>{
console.log('删除文档单击');
文档。删除EventListener(“单击”,关闭);
},[开放];
}
因此您希望将该函数抛出useffect()
钩子中,并使用useRef
如下所示:
import React, { useEffect, useState, useRef } from 'react';
const MyComponent = ({ options, selected }) => {
const [open, setOpen] = useState(false);
const ref = useRef();
useEffect(() => {
const setupDocumentClickEffect = (event) => {
// this if conditional logic assumes React v17
if (ref.current && ref.current.contains(event.target)) {
return;
}
setOpen(false);
};
document.body.addEventListener('click', setupDocumentClickEffect);
return () => {
document.body.removeEventListener('click', setupDocumentClickEffect);
};
}, []);
}
因此,由于它是一个菜单,我设想您通过map()
函数在某个地方构建列表,在本例中,我调用options
,这就是为什么您在MyComponent
中看到它作为道具传递,并且您希望从菜单中呈现该选项列表:
import React, { useEffect, useState, useRef } from 'react';
const MyComponent = ({ label, options, selected, onSelectedChange }) => {
const [open, setOpen] = useState(false);
const ref = useRef();
useEffect(() => {
const setupDocumentClickEffect = (event) => {
// this if conditional logic assumes React v17
if (ref.current && ref.current.contains(event.target)) {
return;
}
setOpen(false);
};
document.body.addEventListener('click', setupDocumentClickEffect);
return () => {
document.body.removeEventListener('click', setupDocumentClickEffect);
};
}, []);
const renderedOptions = options.map((option) => {
if (option.value === selected.value) {
return null;
}
return (
<div
key={option.value}
className="item"
onClick={() => {
onSelectedChange(option);
}}
>
{option.label}
</div>
);
});
return (
<div ref={ref} className="ui form">
// the rest of your JSX code here including
// renderedOptions below
{renderedOptions}
</div>
);
};
export default MyComponent;
import React,{useffect,useState,useRef}来自'React';
const MyComponent=({label,options,selected,onSelectedChange})=>{
const[open,setOpen]=useState(false);
const ref=useRef();
useffect(()=>{
常量设置文档ClickEffect=(事件)=>{
//此if条件逻辑假定为React v17
if(ref.current&&ref.current.contains(event.target)){
返回;
}
setOpen(假);
};
document.body.addEventListener('click',setupDocumentClickEffect);
return()=>{
document.body.removeEventListener('click',setupDocumentClickEffect);
};
}, []);
const renderoptions=options.map((选项)=>{
如果(option.value==selected.value){
返回null;
}
返回(
{
选择变更(选项);
}}
>
{option.label}
);
});
返回(
//这里剩下的JSX代码包括
//下面的渲染选项
{renderoptions}
);
};
导出默认MyComponent;
因此,我为您的MyComponent
添加了一些道具,并向您展示了如何实现useRef
,这对于实现这一点也很重要