requestAnimationFrame计时器bug:设置winnerPlayerId后无法立即停止
问题分析与解决方案
问题出在你没有清理requestAnimationFrame的循环任务。当winnerPlayerId或turnTimerStartedAt变化时,旧的useEffect实例里的tick函数还在通过requestAnimationFrame持续执行,不断调用setValue,导致你设置的setValue(null)被覆盖,无法立即停止计时器。
要解决这个问题,需要在useEffect的清理函数中取消未完成的动画帧请求,确保每次依赖变化时终止之前的计时器循环。
修改后的代码
useEffect(() => { let animationId; // 保存动画帧ID,用于后续取消 if (turnTimerStartedAt && !winnerPlayerId) { const startedAt = Date.now() - (Rune.gameTimeInSeconds() - turnTimerStartedAt) * 1000; const tick = () => { const newValue = MAX_TURN_TIME - (Date.now() - startedAt) / 1000 - 0.5; if (newValue <= 0) { setValue(0); } else { setValue(newValue); animationId = requestAnimationFrame(tick); // 保存当前动画帧ID } }; animationId = requestAnimationFrame(tick); // 启动第一个动画帧并记录ID } else { setValue(null); } // 清理函数:组件卸载或依赖变化时,终止之前的动画循环 return () => { if (animationId) { cancelAnimationFrame(animationId); } }; }, [turnTimerStartedAt, winnerPlayerId]);
关键说明
- 新增
animationId变量存储requestAnimationFrame的返回值,用于追踪当前运行的动画帧任务。 useEffect的返回函数会在组件卸载或依赖项变化时执行,调用cancelAnimationFrame(animationId)可以立即终止旧的计时器循环。- 当
winnerPlayerId被设置后,旧的动画循环会被立刻停止,setValue(null)能直接生效,计时器和动画会马上进入闲置状态。
内容的提问来源于stack exchange,提问作者MarK
相关产品推荐
相关产品推荐

