Reactjs 为什么在useEffect回调之后立即运行React useEffect清理函数,然后再也不会运行了?

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

我有一个带有组件的小React应用程序,该组件有一个打开小菜单的按钮,我希望当用户单击组件外的任何位置时,它能关闭菜单

函数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
,这对于实现这一点也很重要