【问题标题】:Setting an interval on a click event in React using Hooks won't use updated state values在 React 中使用 Hooks 设置点击事件的时间间隔不会使用更新的状态值
【发布时间】:2020-10-10 12:33:51
【问题描述】:

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

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

我不知道如何修复它,也不知道我如何错误地使用状态变量和局部变量。

我在 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 一起使用的最佳方法。

【问题讨论】:

  • 你在哪里使用handleToggle
  • @GalAbra 抱歉。我忘了把它包括在样本中。刚刚添加。

标签: javascript reactjs typescript state setinterval


【解决方案1】:

为了“订阅”一个回调函数来改变状态,你绝对可以使用useEffect钩子:

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

useEffect(automaticallySetMouseOutside, [mouseInside]);

useEffect 的第二个参数订阅 automaticallySetMouseOutside 以便在 mouseInside 更改时被调用。

虽然这段代码是为了举例说明这个钩子的用法,但它并不准确 - 因为它可以在用户还在里面时关闭菜单。
如果您想全面解决您的问题,请查看此CodeSandBox

【讨论】:

  • 谢谢。我让它工作了。我必须像你建议的那样使用效果,并且只有在用户单击下拉菜单时才能创建间隔。不过我不得不稍微修改一下。
  • 很高兴听到!通常最好使用not to use setInterval,因为它会导致性能问题
  • 是的,这是我最关心的问题。我在网上找到的大多数示例都依赖于始终运行的间隔,但我只希望它在菜单打开时运行以避免性能问题。我发布了我的更新版本,因为它确实与您的示例有很大不同,但您让我朝着正确的方向前进 :)
【解决方案2】:

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);
    }
  };

【讨论】:

  • 感谢您的建议。我最终使用 useEffect 来确保我对间隔有相同的引用。
  • 请注意,因为您在效果内声明let interval,每次进入效果时您声明新的interval...考虑在效果外使用let interval = useRef(null),并分配或清除@改为 987654326@
  • 谢谢。我更新了我的解决方案。我认为使用interval.current = setInterval 仍然意味着我每次进入效果时都会创建一个新间隔?
  • 我不得不在没有 useRef 的情况下编辑答案,因为这会导致打开多个选项卡时出现问题,并且在每个选项卡上呈现组件。很奇怪。
【解决方案3】:

我最终使用了@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>
  );

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-01-25
    • 1970-01-01
    • 1970-01-01
    • 2019-09-11
    • 2020-05-12
    • 1970-01-01
    • 2021-03-16
    • 2020-12-13
    相关资源
    最近更新 更多