ReactJS 在 UseEffect 中使用 SetInterval 导致状态丢失

Posted

技术标签:

【中文标题】ReactJS 在 UseEffect 中使用 SetInterval 导致状态丢失【英文标题】:ReactJS Use SetInterval inside UseEffect Causes State Loss 【发布时间】:2021-07-28 08:41:11 【问题描述】:

所以我在 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 来回切换。

a screenshot of the console message

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

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

【问题讨论】:

我看不到你在哪里设置状态。 在功能组件 app() 的return(...) 中,我有一个按钮&lt;Button onClick=()=&gt; setShowCanvas(false)&gt;Hide Canvas&lt;/Button&gt; 所以当我点击那个按钮时,showCanvas 的状态应该设置为false? 请将其添加到代码中。您的示例应该是可重现的。这意味着我们至少应该从您的代码中看到您的登录名是什么,以及错误可能发生在哪里。 ***.com/help/minimal-reproducible-example 设置为false后,在哪里又设置为true?丢失的状态是什么?你能澄清你的问题吗? 嗨,很抱歉造成混乱。我添加了控制台的屏幕截图,希望这可以澄清我的问题。所以在我的例子中,showCanvas 状态最初是true(这是有道理的),我点击hide canvas 按钮,状态设置为false,它应该保持false,直到我点击@987654335 @ 按钮。但是如果你看截图,你会看到状态不断地从falsetrue 来回切换,虽然我只点击了一次hide canvas,没有别的... 【参考方案1】:

尝试将 useCallback 用于 runFaceDots 函数 - 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])

     .....

【讨论】:

这确实做了一些事情,但仍然不能正常工作。我在useEffect(()=&gt;...,[showCanvas] 中添加了console.log(showCanvas),控制台返回了理想的结果。但是,我还在timer = setInterval(() =&gt; ...,10)函数中添加了console.log(showCanvas),但是即使我点击了hide canvas按钮,控制台总是返回true(这是showCanvas的初始值)... 更新了答案。您可以使用全局变量来保存计时器,并在您隐藏画布时清除间隔。 完美运行。谢谢!

以上是关于ReactJS 在 UseEffect 中使用 SetInterval 导致状态丢失的主要内容,如果未能解决你的问题,请参考以下文章

在 ReactJS 的 useEffect 中定义后 useEffect 值保持不变

如何从 Reactjs 中的 useEffect 挂钩访问道具

(reactjs) useEffect 没有在后台运行

ReactJS:如何只调用一次 useEffect 挂钩来获取 API 数据

删除组件后如何清理react js中的useEffect函数

ReactJs/NextJs useEffect() 总是被调用两次