首页 > 解决方案 > ReactJS 在 UseEffect 中使用 SetInterval 导致状态丢失

问题描述

所以我在 create-react-app 中编写了一个产品原型,在我的 App.js 中,在 app() 函数中,我有:

const [showCanvas, setShowCanvas] = useState(true)

此状态由具有 onClick 功能的按钮控制;然后我有一个函数,在它里面,detectDots 函数应该在一个区间内运行:

const runFaceDots = async (key, dot) => {
const net = await facemesh.load(...);
setInterval(() => {
  detectDots(net, key, dot);
}, 10);
// return ()=>clearInterval(interval);};

detectDots 函数的工作方式如下:

  const detectDots = async (net, key, dot) => {
...
  console.log(showCanvas);
  requestFrame(()=>{drawDots(..., showCanvas)});
  }
}};

我有一个这样的 useEffect :

useEffect(()=>{
runFaceDots(); return () => {clearInterval(runFaceDots)}}, [showCanvas])

最后,我可以通过单击这两个按钮来更改状态:

 return (
     ...
      <Button 
        onClick={()=>{setShowCanvas(true)}}>
          Show Canvas
      </Button>
      <Button 
        onClick={()=> {setShowCanvas(false)}}>
          Hide Canvas
      </Button>
    ...
    </div>);

我在网上查了几篇帖子,说不清除interval会导致状态丢失。就我而言,我从 useEffect 看到了一些奇怪的行为:当我使用 onClick 设置 ShowCanvas(false) 时,控制台显示 console.log(showCanvas) 不断地从 true 到 false 来回切换。

控制台消息的屏幕截图

您最初可以看到,showCanvas 状态为 true,这是有道理的。但是当我单击“隐藏画布”按钮时,我只单击了一次,showCanvas 设置为 false,它应该保持为 false,因为我没有单击“显示画布”按钮。

我很困惑,希望有人能提供帮助。

标签: reactjsreact-hookssetintervaluse-effectclearinterval

解决方案


尝试使用 useCallbackrunFaceDots函数 - https://reactjs.org/docs/hooks-reference.html#usecallback

并确保您返回 setInterval 变量以清除计时器。

const runFaceDots = useCallback(async (key, dot) => {
     const net = await facemesh.load(...);
     const timer = setInterval(() => {
        detectDots(net, key, dot);
     }, 10);
     return timer //this is to be used for clearing the interval
 },[showCanvas])

然后将 useEffect 更改为此 - 仅当 showCanvas 为 true 时才运行该函数

useEffect(()=>{
       if (showCanvas) {
       const timer = runFaceDots(); 
        return () => {clearInterval(timer)}
       }
       }, [showCanvas])

更新:使用全局计时器

let timer // <-- create the variable outside the component.

const MyComponent = () => {
     .....
    useEffect(()=>{
           if (showCanvas) {
           runFaceDots();  // You can remove const timer here
            return () => {clearInterval(timer)}
           } else {
               clearInterval(timer) //<-- clear the interval when hiding
           }
            
           }, [showCanvas])

    const runFaceDots = useCallback(async (key, dot) => {
         const net = await facemesh.load(...);
         timer = setInterval(() => { //<--- remove const and use global variable
            detectDots(net, key, dot);
         }, 10);
         return timer //this is to be used for clearing the interval
     },[showCanvas])

     .....
}

推荐阅读