React使用useEffect实现倒计时时setInterval未被正常清除问题
问题根本原因
- 你编写的
useEffect确实正确将timeout加入了依赖数组,每次timeout更新时,会先执行上一轮返回的清理函数清除旧定时器,再创建新的定时器。 - 当
timeout被更新为0的这一轮组件渲染完成后,useEffect会正常执行并创建一个新的定时器,这个定时器的回调函数通过闭包捕获的timeout值固定为当前的0。 - 因为回调中只有
timeout>0时才会调用状态更新函数修改timeout,当timeout为0时不会触发状态更新,timeout的值会永久停留在0,useEffect的依赖没有发生变化,就不会再触发执行,也就不会清理这个最后创建的定时器,因此它会一直每秒执行一次,打印日志。
额外提示:你把状态名命名为
timeout,状态更新函数命名为setTimeout,和全局的window.setTimeout重名,容易出现变量指向错误,建议调整命名避免混淆。
修复方案
方案1:创建定时器前判断倒计时是否结束,为0则不创建定时器
function CounterGame() { const [timeout, setTimeout] = React.useState(3); React.useEffect(() => { // 倒计时结束直接返回,不创建定时器 if (timeout <= 0) return; let id = window.setInterval(() => { setTimeout(x => x - 1 ); console.log("in setInterval", timeout); }, 1000) return () => { window.clearInterval(id); } }, [timeout]) return ( <div className="App"> See instructions. <h1>{timeout} seconds</h1> {!timeout && <button>Click</button>} </div> ); }
方案2:定时器回调中主动判断结束条件,满足则清除当前定时器
function CounterGame() { const [timeout, setTimeout] = React.useState(3); React.useEffect(() => { let id = window.setInterval(() => { if (timeout > 0) { setTimeout(x => x - 1 ); } else { // 倒计时结束主动清理当前定时器 window.clearInterval(id); } console.log("in setInterval", timeout); }, 1000) return () => { window.clearInterval(id); } }, [timeout]) return ( <div className="App"> See instructions. <h1>{timeout} seconds</h1> {!timeout && <button>Click</button>} </div> ); }
方案3:改用setTimeout实现,更适配倒计时递减场景
function CounterGame() { const [timeout, setTimeout] = React.useState(3); React.useEffect(() => { if (timeout <= 0) return; let id = window.setTimeout(() => { setTimeout(x => x - 1 ); }, 1000) return () => { window.clearTimeout(id); } }, [timeout]) return ( <div className="App"> See instructions. <h1>{timeout} seconds</h1> {!timeout && <button>Click</button>} </div> ); }
内容的提问来源于stack exchange,提问作者xuanyue
相关产品推荐
相关产品推荐

