javascript - setInterval + React 钩子导致组件内的多次更新
问题描述
我正在构建一个秒表 UI,以秒为单位显示时间。单击按钮,计时器将开始向上计数,并在再次单击时停止。用户应该能够再次启动它。
我遇到的问题是我可以setInterval
正常工作,但是一旦我包含setTime
钩子,组件就会更新以在 UI 中呈现时间,但setInterval
实例被多次调用。这会导致奇怪的渲染行为。
const Timer = () => {
const [time, setTime] = useState(0)
let timer
const startStopTimer = () => {
if (!timer) timer = setInterval(() => setTime(time++), 1000)
else {
clearInterval(timer)
timer = null
}
}
return (
<div>
<p>Time: {time} seconds</p>
<Button
onClick={() => {
startStopTimer()
}
> Start/Stop </Button>
</div>
)
}
示例行为是:
- 用户单击开始/停止
- 定时器从 0 开始向上计数
- 用户单击开始/停止
- 定时器立即停止
- 用户单击开始/停止
- 计时器从停止的地方继续
解决方案
这是 React 钩子中陈旧闭包的经典示例,在您的 setInterval 值time
在调用后不会改变setTime
。更改您的代码:
setInterval(() => setTime(currentTime => currentTime + 1), 1000)
.
setTime
就像setState
有类组件一样,它也接受一个回调函数,该函数将当前值作为第一个参数
此外,该timer
变量在您的代码中是无用的,因为在每次重新渲染时它将是未定义的,并且您将无法访问 . 的返回值setInterval
,因此它将重新初始化setInterval
. 为了处理这种使用useRef
,您可以存储 in 的返回值,setInterval
在.current
后续重新渲染后您可以使用它,因此不再重新初始化 setInterval 并且您也可以使用clearInterval
解决方案:
const {useState, useRef} = React;
const {render} = ReactDOM;
const Timer = () => {
const [time, setTime] = useState(0);
const timer = useRef(null);
const startStopTimer = () => {
if (!timer.current) {
timer.current = setInterval(() => setTime(currentTime => currentTime + 1), 1000);
} else {
clearInterval(timer.current);
timer.current = null;
}
};
return (
<div>
<p>Time: {time} seconds</p>
<button
onClick={startStopTimer}
>
Start/Stop
</button>
</div>
);
};
render(<Timer />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<div id="root"></div>
推荐阅读
- django - 未调用覆盖的清理函数
- python - 如何使用“matplotlib.pyplot”在不同的窗口中绘制我的图表?
- c++ - 如何在 Qt creator 4.8 中使用 C++11
- php - 将对象值替换为数组
- reactjs - 你能同时使用 React-hooks 和 redux 吗?
- c++ - 使用来自 SYSTEM 级别进程(Windows 服务)的登录用户上下文来模拟和运行任何方法:
- google-bigquery - Bigquery 命令行中的 --job_property
- excel - 缺少 Excel 库导致不需要/使用它的用户的参考错误
- java - 创建名为“serviceController”的 bean 时出错:自动装配依赖项的注入失败;
- angular - 在 2 个子组件之间传递变量会产生 ExpressionChangedAfterItHasBeenCheckedError 错误