如何在 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,以便它们可以在另一个组件中呈现?
如何在带有 TypeScript 的 React-Native 中使用 styled-component 键入无状态功能组件?