我正在制作一个简单的进度栏,当我使用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
函数时, inside setInterval countInterval
值完全没有改变。无论如何,countInterval
状态已更改。
要解决这个问题,我在progress
函数内部使用了变量,如下所示:
const progress = () => {
let internalValue = 0
intervalID = setInterval(() => {
setCountInterval(internalValue)
internalValue++
if(internalValue > 100) {
setCountInterval(0)
clearInterval(intervalID)
}
},100)
}
那很好,但是我仍然想知道是否有更好的方法,在第一种情况下我做错了什么。
第二个问题是我无法清除progress
函数外部的间隔,并且不确定在这里做错了什么还是错过了什么?预先感谢您的任何帮助和建议。
答案 0 :(得分:3)
您的问题是由于定时器参考在重新渲染时丢失而引起的,并且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>
);
};
答案 1 :(得分:2)
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]);