将 useState 与 setInterval 内的 if 条件一起使用不起作用
Using useState with an if conditional inside of setInterval isn't working
import { useState, useEffect } from "react";
import "./styles.css";
export default function App() {
const [progress, setProgress] = useState(0);
let progressTimer;
function handleTime() {
if (progress <= 100) {
console.log("Progress: " + progress);
setProgress((prevState) => (prevState += 10));
} else {
console.log("greater");
clearInterval(progressTimer);
}
}
function handlePlay() {
console.log("Timer start");
progressTimer = setInterval(handleTime, 1000);
}
useEffect(() => {
handlePlay();
});
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
{progress}
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
期望的结果:转到 100,每 1 秒数 10。超过 100 后,关闭计时器。
实际结果:它一直在上升,每1秒超过10。
setInterval
对于 React,在第一次渲染时调用时,将导致间隔回调在第一次渲染后有状态变量的陈旧关闭。
我会改用 setTimeout
,这样每当回调运行时,它就会具有最新状态的范围。
const { useState, useEffect } = React;
function App() {
const [progress, setProgress] = useState(0);
function handleTime() {
if (progress <= 100) {
console.log("Progress: " + progress);
setProgress((prevState) => (prevState += 10));
} else {
console.log("greater");
}
}
useEffect(() => {
const timerId = setTimeout(handleTime, 1000);
return () => clearTimeout(timerId);
});
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
{progress}
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
ReactDOM.render(<App />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div class='react'></div>
问题
- 启动间隔的
useEffect
没有依赖数组,因此每次渲染组件时都会启动一个新的间隔。这就是导致跳得越来越大的原因。
- 每个渲染周期都会重新声明
progressTimer
,因此无法清除它。
- 当传递给
setInterval
回调时,progress
状态的检查在回调范围内关闭。您只会查看初始状态值。换句话说,这是一个陈旧的外壳。
- 在功能状态更新中使用
prevState => (prevState += 10)
实际上会改变先前的状态。应避免所有状态突变。
一个解决方案
- 向
useEffect
添加一个依赖数组,以便它在组件安装时运行一次。将 handlePlay
逻辑移动到效果回调中,以便在安装时没有外部依赖性。不要忘记 return 清除函数以在组件卸载时清除任何 运行 间隔。
- 将
progressTimer
存储为 React 引用,使其成为稳定的引用。
- 解除状态更新与清除计时器副作用的混合。使用第二个
useEffect
挂钩检查当前 progress
值何时达到 100。
- 在状态更新器中只是return前一个状态加10,
prevState => prevState + 10
作为下一个状态值。
代码
function App() {
const [progress, setProgress] = useState(0);
const progressTimer = useRef();
function handleTime() {
setProgress((prevState) => prevState + 10);
}
useEffect(() => {
console.log("Progress: " + progress);
if (progress >= 100) clearInterval(progressTimer.current);
}, [progress]);
useEffect(() => {
console.log("Timer start");
progressTimer.current = setInterval(handleTime, 1000);
return () => clearInterval(progressTimer.current);
}, []);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
{progress}
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
import { useState, useEffect } from "react";
import "./styles.css";
export default function App() {
const [progress, setProgress] = useState(0);
let progressTimer;
function handleTime() {
if (progress <= 100) {
console.log("Progress: " + progress);
setProgress((prevState) => (prevState += 10));
} else {
console.log("greater");
clearInterval(progressTimer);
}
}
function handlePlay() {
console.log("Timer start");
progressTimer = setInterval(handleTime, 1000);
}
useEffect(() => {
handlePlay();
});
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
{progress}
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
期望的结果:转到 100,每 1 秒数 10。超过 100 后,关闭计时器。
实际结果:它一直在上升,每1秒超过10。
setInterval
对于 React,在第一次渲染时调用时,将导致间隔回调在第一次渲染后有状态变量的陈旧关闭。
我会改用 setTimeout
,这样每当回调运行时,它就会具有最新状态的范围。
const { useState, useEffect } = React;
function App() {
const [progress, setProgress] = useState(0);
function handleTime() {
if (progress <= 100) {
console.log("Progress: " + progress);
setProgress((prevState) => (prevState += 10));
} else {
console.log("greater");
}
}
useEffect(() => {
const timerId = setTimeout(handleTime, 1000);
return () => clearTimeout(timerId);
});
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
{progress}
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
ReactDOM.render(<App />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div class='react'></div>
问题
- 启动间隔的
useEffect
没有依赖数组,因此每次渲染组件时都会启动一个新的间隔。这就是导致跳得越来越大的原因。 - 每个渲染周期都会重新声明
progressTimer
,因此无法清除它。 - 当传递给
setInterval
回调时,progress
状态的检查在回调范围内关闭。您只会查看初始状态值。换句话说,这是一个陈旧的外壳。 - 在功能状态更新中使用
prevState => (prevState += 10)
实际上会改变先前的状态。应避免所有状态突变。
一个解决方案
- 向
useEffect
添加一个依赖数组,以便它在组件安装时运行一次。将handlePlay
逻辑移动到效果回调中,以便在安装时没有外部依赖性。不要忘记 return 清除函数以在组件卸载时清除任何 运行 间隔。 - 将
progressTimer
存储为 React 引用,使其成为稳定的引用。 - 解除状态更新与清除计时器副作用的混合。使用第二个
useEffect
挂钩检查当前progress
值何时达到 100。 - 在状态更新器中只是return前一个状态加10,
prevState => prevState + 10
作为下一个状态值。
代码
function App() {
const [progress, setProgress] = useState(0);
const progressTimer = useRef();
function handleTime() {
setProgress((prevState) => prevState + 10);
}
useEffect(() => {
console.log("Progress: " + progress);
if (progress >= 100) clearInterval(progressTimer.current);
}, [progress]);
useEffect(() => {
console.log("Timer start");
progressTimer.current = setInterval(handleTime, 1000);
return () => clearInterval(progressTimer.current);
}, []);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
{progress}
<h2>Start editing to see some magic happen!</h2>
</div>
);
}