状态不会在 setInterval 中更新



我正在制作一个简单的进度条,当我使用 setInterval 时,我注意到钩子状态的奇怪行为。这是我的示例代码:

const {useState} = React;
const Example = ({title}) => {
const [count, setCount] = useState(0);
const [countInterval, setCountInterval] = useState(0)

let intervalID

const handleCount = () => {
setCount(count + 1)
console.log(count)
}  

const progress = () => {
intervalID = setInterval(() => {
		setCountInterval(countInterval => countInterval + 1)
		console.log(countInterval)
		if(countInterval > 100) { // this is never reached
setCountInterval(0)
clearInterval(intervalID)
		}
	},100)
}

const stopInterval = () => {
clearInterval(intervalID)
}

return (
<div>
<p>{title}</p>
<p>You clicked {count} times</p>
<p>setInterval count { countInterval } times</p>
<button onClick={handleCount}>
Click me
</button>
<button onClick={progress}>
Run interval
</button>
<button onClick={stopInterval}>
Stop interval
</button>
</div>
);
};
// Render it
ReactDOM.render(
<Example title="Example using simple hook:" />,
document.getElementById("app")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="app"></div>

如果我通过handleCount设置状态,一切都会按预期发生,但是当我运行progress函数时,在setInterval内部countInterval值根本不会改变。无论如何,该州的countInterval都发生了变化。

为了解决这个问题,我在函数中使用progress变量,如下所示:

const progress = () => {
let internalValue = 0
intervalID = setInterval(() => {
setCountInterval(internalValue)
internalValue++
if(internalValue > 100) {
setCountInterval(0)
clearInterval(intervalID)
}
},100)
}

这很好用,但我仍然想知道是否有更好的方法以及我在第一种情况下做错了什么。

第二个问题是我无法清除progress函数之外的间隔,我不确定我在这里做错了什么或我错过了什么?提前感谢您的任何帮助和建议。

您的问题是由重新渲染时丢失计时器引用以及setInterval回调引用过时版本的setCountInterval引起的。

为了使它完全正常工作,我建议添加一个状态变量来跟踪它是否已启动,并添加一个useEffect来处理setInterval的设置和清除。

const Example = ({ title }) => {
const [count, setCount] = useState(0);
const [countInterval, setCountInterval] = useState(0);
const [started, setStarted] = useState(false);
const handleCount = () => {
setCount(count + 1);
console.log(count);
};
useEffect(() => {
let intervalID;
if (started) {
intervalID = setInterval(() => {
setCountInterval(countInterval => countInterval + 1);
console.log(countInterval);
if (countInterval > 100) {
setCountInterval(0);
setStarted(false);
}
}, 100);
} else {
clearInterval(intervalID);
}
return () => clearInterval(intervalID);
}, [started, countInterval]);
return (
<div>
<p>{title}</p>
<p>You clicked {count} times</p>
<p>setInterval count {countInterval} times</p>
<button onClick={handleCount}>Click me</button>
<button onClick={() => setStarted(true)}>Run interval</button>
<button onClick={() => setStarted(false)}>Stop interval</button>
</div>
);
};

此处的工作沙盒:https://codesandbox.io/s/elegant-leaf-h03mi

countInterval是一个局部变量,包含一个基元值。根据 JavaScript 的本质,setState无法以任何方式改变该变量。相反,它会重新执行整个Example函数,然后useState将返回新的更新值。这就是在组件重新渲染之前无法访问更新状态的原因。

通过将条件移动到状态更新回调中,可以轻松解决您的问题:

setCountInterval(countInterval => countInterval > 100 ? 0 : countInterval + 1)
由于重新渲染,

您不能使用局部变量,因此每次重新渲染时都会重新创建intervalId变量(并且其值会丢失(。使用useRef跨重新渲染使用值。

const interval = useRef(undefined);
const [count, setCount] = useState(0);
function stop() { 
if(!interval.current) return;
clearInterval(interval.current);
interval.current = null;
}
function start() {
if(!interval.current) interval.current = setInterval(() => {
setCount(count => count > 100 ? 0 : count + 1);
});
}
useEffect(() => {
if(count >= 100) stop();
}, [count]);

相关内容

  • 没有找到相关文章

最新更新