如何在 React 函数式组件中正确设置 setInterval 定时器?
How to Setup a setInterval Timer Properly in a React Functional Component?
我刚开始学习 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)
显然不行
问题:在您当前的实现中,每次组件呈现时都会调用 setInterval
(即,也会在设置时间状态后调用)并且会 创建一个新的interval - 它产生了在您的控制台中看到的这种“指数增长”。
如评论部分所述: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
If you want to run an effect and clean it up only once (on mount and
unmount), you can pass an empty array ([]) as a second argument. This
tells React that your effect doesn’t depend on any values from props
or state, so it never needs to re-run.
在您的场景中,这是“空数组作为第二个参数”的完美用法,因为您只需要在安装组件时设置时间间隔并在卸载时清除时间间隔。看看 useEffect
returns 的函数。这是我们的清理函数,它将在组件卸载时 运行。这将“清理”或者在这种情况下,清除组件不再使用时的时间间隔。
我编写了一个小型应用程序来演示我在这个答案中涵盖的所有内容:https://codesandbox.io/s/so-react-useeffect-component-clean-up-rgxm0?file=/src/App.js
我加入了一个小的路由功能,以便可以观察到组件的“卸载”。
我的旧答案(不推荐):
每次组件 re-renders 都会创建一个新的间隔,这就是您为时间设置新状态时发生的情况。我要做的是在设置新间隔之前清除之前的间隔 (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 hook,函数里面的return可以使用clearInterval函数,推荐你查查,useEffect很适合什么你想做什么。
正如 Macro Amorim 回答的那样,useEffect
是最好的方法。这里的代码:
useEffect(() => {
const interval = setInterval(() => {
const newTime = new Date().toLocaleTimeString();
setTime(newTime);
}, 1000)
return () => {
clearInterval(interval);
}
}, [time])
你也可以试试这个-
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>
);
}
使用 Hooks 的简单计时器
import React, { useEffect, useState } from "react";
const TimeHeader = () => {
const [timer, setTimer] = useState(new Date());
useEffect(() => {
const interval = setInterval(() => {
setTimer(new Date());
}, 1000);
}, []);
return (
<div className="electonTimer">
<div className="electionDate">
<h1>{timer.toLocaleTimeString()}</h1>
</div>
</div>
);
};
我刚开始学习 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)
显然不行
问题:在您当前的实现中,每次组件呈现时都会调用 setInterval
(即,也会在设置时间状态后调用)并且会 创建一个新的interval - 它产生了在您的控制台中看到的这种“指数增长”。
如评论部分所述: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
If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run.
在您的场景中,这是“空数组作为第二个参数”的完美用法,因为您只需要在安装组件时设置时间间隔并在卸载时清除时间间隔。看看 useEffect
returns 的函数。这是我们的清理函数,它将在组件卸载时 运行。这将“清理”或者在这种情况下,清除组件不再使用时的时间间隔。
我编写了一个小型应用程序来演示我在这个答案中涵盖的所有内容:https://codesandbox.io/s/so-react-useeffect-component-clean-up-rgxm0?file=/src/App.js
我加入了一个小的路由功能,以便可以观察到组件的“卸载”。
我的旧答案(不推荐):
每次组件 re-renders 都会创建一个新的间隔,这就是您为时间设置新状态时发生的情况。我要做的是在设置新间隔之前清除之前的间隔 (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 hook,函数里面的return可以使用clearInterval函数,推荐你查查,useEffect很适合什么你想做什么。
正如 Macro Amorim 回答的那样,useEffect
是最好的方法。这里的代码:
useEffect(() => {
const interval = setInterval(() => {
const newTime = new Date().toLocaleTimeString();
setTime(newTime);
}, 1000)
return () => {
clearInterval(interval);
}
}, [time])
你也可以试试这个-
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>
);
}
使用 Hooks 的简单计时器
import React, { useEffect, useState } from "react";
const TimeHeader = () => {
const [timer, setTimer] = useState(new Date());
useEffect(() => {
const interval = setInterval(() => {
setTimer(new Date());
}, 1000);
}, []);
return (
<div className="electonTimer">
<div className="electionDate">
<h1>{timer.toLocaleTimeString()}</h1>
</div>
</div>
);
};