使用 Hooks 在 React 中设置点击事件的时间间隔不会使用更新的状态值

Setting an interval on a click event in React using Hooks won't use updated state values

我正在尝试创建一个过滤器下拉菜单,如果用户没有手动关闭它并且在 1 秒后将鼠标从过滤器上移开,它会自行隐藏。

问题是 mouseInside 布尔状态永远不会在区间内更改为 false。我相信这是因为反应组件被重新渲染但不完全确定。

我不知道如何修复它或我可能如何错误地使用状态变量与局部变量。

我在带有钩子的 React 中使用 TypeScript:

const Filter: React.FC = () => {
  const [shown, setShown] = useState<boolean>(false);
  const [mouseInside, setMouseInside] = useState<boolean>(false);
  
  let interval: NodeJS.Timeout | null = null;

  const handleToggle = () => {
      setShown(!shown);
      
      interval = setInterval(() => {
        console.log(mouseInside);

        // mouseInside is permanently true
        if (!mouseInside && interval) {
           // this never gets triggered
           clearInterval(interval);
           setShown(false);
           console.log("Finished!");
        }
      }, 1000);
    }
  };

  return (
    <div
      style={{ width: 100, height: 100, backgroundColor: "lightgray" }}  
      onClick={handleToggle}
      onMouseEnter={() => setMouseInside(true)}
      onMouseLeave={() => setMouseInside(false)}
    >
        <p>Click me!</p>
        
        {shown && <div>drop down here...</dv>}
    </div>
    );
};

我需要 mouseInside 在用户离开过滤框时最终变为 false。 onMouseLeave 事件肯定会被触发,但我认为这是导致问题的 React 方面。

也许我需要使用 useEffect?我不知道如何将其与 setInterval.

一起使用的最佳方法

为了“订阅”状态变化的回调函数,您绝对可以使用 useEffect 钩子:

function automaticallySetMouseOutside() {  
  setTimeout(() => {
    if (mouseInside) {
      setMouseInside(false);
    }
  }, 1000);
}

useEffect(automaticallySetMouseOutside, [mouseInside]);

useEffect 的第二个参数订阅 automaticallySetMouseOutside 以便在 mouseInside 发生变化时被调用。

虽然此代码旨在举例说明此挂钩的用法,但它并不准确 - 因为它可以在用户仍在菜单中时关闭菜单。
如果您想完整解决您的问题,请查看此 CodeSandBox

虽然在 handleToggle 内声明间隔操作,但它存在于自己的范围内,与实际状态没有绑定,

const handleInterval = interval => { 
        console.log(mouseInside);
        if (!mouseInside && interval) {
           // this never gets triggered
           clearInterval(interval);
           setShown(false);
           console.log("Finished!");
        }
      }
   
const handleToggle = () => {
      interval = setInterval(() => {
        () => handleInterval(interval),
      }, 1000);
    }
  };

我最终使用了@GalAbra 建议的部分内容,但我只想在用户单击切换器时创建间隔,并且仅在菜单打开时创建:

  const [shown, setShown] = useState<boolean>(false);
  const [mouseInside, setMouseInside] = useState<boolean>(false);

  useEffect(() => {
    let interval: NodeJS.Timeout | null;

    if (shown) {
      interval = setInterval(() => {
        if (!mouseInside && interval) {
          setShown(false);
        }
      }, 500);
    }

    return () => {
      if (interval) clearInterval(interval);
    };
  }, [shown, mouseInside]);

  return (
    <div
      style={{ width: 100, height: 100, backgroundColor: "lightgray" }}  
      onClick={() => setShown(!shown)}
      onMouseEnter={() => setMouseInside(true)}
      onMouseLeave={() => setMouseInside(false)}
    >
        <p>Click me!</p>        
        {shown && <div>drop down here...</dv>}
    </div>
  );