如何在 React 功能组件中正确设置 setInterval 计时器?

Posted

技术标签:

【中文标题】如何在 React 功能组件中正确设置 setInterval 计时器?【英文标题】:How to Setup a setInterval Timer Properly in a React Functional Component? 【发布时间】:2020-11-29 16:28:03 【问题描述】:

我刚开始学习 React,我正在看一个处理状态和钩子的教程。它只处理每 1000 毫秒更新一次的时间(或者我认为是这样)。

import React from "react";
let count = 0;

function App() 
  const now = new Date().toLocaleTimeString();
  let [time, setTime] = React.useState(now);


  function updateTime()
    const newTime = new Date().toLocaleTimeString();
    setTime(newTime);
    count++;
    console.log(count);
    console.log(new Date().getMilliseconds());
  

  setInterval(updateTime, 1000);


  return (
    <div className="container">
      <h1>time</h1>
      <button onClick = updateTime>time</button>
    </div>
  );


export default App;

本教程的目的只是一个关于如何更新时间的简单示例,但我注意到它每 1000 毫秒更新多次(以突发形式)。我怀疑每次更改挂钩时都会呈现新组件,但旧组件仍然在那里更新并产生更多组件,导致每 1000 毫秒调用似乎呈指数增长。

我很好奇这里发生了什么?我将如何去假设有一个每 1000 毫秒更新一次的简单计数器? setTime(count) 显然不行

【问题讨论】:

本文专门介绍了函数式react组件中的setIntervaloverreacted.io/making-setinterval-declarative-with-react-hooks 【参考方案1】:

你也可以试试这个-

import React,  useEffect, useState  from 'react';

export default function App() 
  const [timer, setTimer] = useState(0);
  const [toggle, setToggle] = useState(false);

  useEffect(() => 
    let counter;
    if (toggle) 
      counter = setInterval(() => setTimer(timer => timer + 1), 1000);
    
    return () => 
      clearInterval(counter);
    ;
  , [toggle]);

  const handleStart = () => 
    setToggle(true);
  ;

  const handleStop = () => 
    setToggle(false);
  ;

  const handleReset = () => 
    setTimer(0);
    setToggle(false);
  ;

  return (
    <div>
      <h1>Hello StackBlitz!</h1>
      <p>Current timer - timer</p>
      <br />
      <button onClick=handleStart>Start</button>
      <button onClick=handleReset>Reset</button>
      <button onClick=handleStop>Stop</button>
    </div>
  );

【讨论】:

请解释为什么您的答案中的代码是正确的而不是转储。【参考方案2】:

问题:在您当前的实现中,每次渲染组件时都会调用setInterval(即,在设置时间状态后也会调用)并将创建一个新间隔 -如您的控制台所示,这产生了这种“指数增长”。

如 cmets 部分所述:useEffect 将是处理 React 中的功能组件时处理这种情况的最佳方式。看看我下面的例子。 useEffect 这里只会在初始组件渲染后(组件挂载时)运行。

React.useEffect(() => 
  console.log(`initializing interval`);
  const interval = setInterval(() => 
    updateTime();
  , 1000);

  return () => 
    console.log(`clearing interval`);
    clearInterval(interval);
  ;
, []); // has no dependency - this will be called on-component-mount

如果你想运行一个效果并且只清理一次(在挂载和 unmount),您可以传递一个空数组 ([]) 作为第二个参数。这 告诉 React 你的效果不依赖于 props 的任何值 或状态,因此它永远不需要重新运行。

在您的场景中,这是“空数组作为第二个参数”的完美用法,因为您只需要在安装组件时设置间隔并在卸载时清除间隔。看看useEffect 也返回的函数。这是我们的清理函数,将在组件卸载时运行。这将“清除”,或者在这种情况下,清除组件不再使用的时间间隔。

我编写了一个小应用程序来演示我在这个答案中涵盖的所有内容:https://codesandbox.io/s/so-react-useeffect-component-clean-up-rgxm0?file=/src/App.js

我已经合并了一个小型路由功能,以便可以观察到组件的“卸载”。


我的旧答案(不推荐):

每次重新渲染组件时都会创建一个新间隔,这就是您为时间设置新状态时发生的情况。我要做的是在设置新间隔之前清除上一个间隔 (clearInterval)

try 
  clearInterval(window.interval)
 catch (e) 
  console.log(`interval not initialized yet`);


window.interval = setInterval(updateTime, 1000);

https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval

【讨论】:

您已经正确识别了问题,但我要提醒您,这不是在 React 函数组件中处理间隔、超时、事件侦听器或订阅的正确方法。 useEffect 将允许您提供设置和清理功能,同时还允许您通过使用依赖数组更好地确定重新渲染期间的娱乐。 @JacobSmit 是正确的。我已经编辑了我的答案,谢谢 我跑了样例代码,发现console.log每次输入两次,但是如果每次都是新的setInterval,不应该每次都成倍增长吗?像 1 2 4 8 等而不是 11 22 33 44 不会有“每次新的setInterval”。在我的代码中,我传递给 useEffect 的函数仅在组件挂载时执行 @XheldonCao 我的意思是为什么一开始的B.James的代码是这样的,而不是useEffect之后...@95faf8e76605e973【参考方案3】:

正如 Macro Amorim 所回答的,useEffect 是做到这一点的最佳方式。代码如下:

useEffect(() => 
    const interval = setInterval(() => 
         const newTime = new Date().toLocaleTimeString();
         setTime(newTime);
    , 1000)

    return () => 
        clearInterval(interval);
    
, [time])

【讨论】:

我必须在您的示例中添加相关的依赖项,例如 time。谢谢【参考方案4】:

在这种情况下你可以使用 React 中的 useEffect 钩子,在函数内部的 return 上你可以使用 clearInterval 函数,我建议你查一下,useEffect 非常适合你想做的事情。

【讨论】:

以上是关于如何在 React 功能组件中正确设置 setInterval 计时器?的主要内容,如果未能解决你的问题,请参考以下文章

如何正确使用带有 React 功能组件的键?这是我的喜欢按钮:

React Native:如何正确地将 renderItem 项传递给 FlatList,以便它们可以在另一个组件中呈现?

Vue之重新渲染组件的正确方式

如何在带有 TypeScript 的 React-Native 中使用 styled-component 键入无状态功能组件?

如何从 React 中的子组件更新父组件(功能)

如何在另一个 React 组件中正确使用 TabNavigator