useInterval回调中loading变量值异常的原因及修复方案
React组件useInterval回调中loading始终为true的原因与修复
在InfoPage组件中,useInterval回调函数内的loading变量始终显示为true,但组件外部传入的loading prop最终会变为false,控制台输出也验证了这一差异。
组件代码
const InfoPage = ({ loading, fetchDataFn }) => { const [lastIntervalTs, setLastIntervalTs] = useState(new Date().valueOf()) const [lastFetchTs, setLastFetchTs] = useState(new Date().valueOf()) const refetchData = () => { const ts = new Date().valueOf() setLastFetchTs(ts) setLastIntervalTs(ts) fetchDataFn() } console.log('Outside callback', loading) useInterval(() => { console.log('Inside callback', loading) if (!loading) { setLastIntervalTs(new Date().valueOf()) } }, 5000) const currentTs = new Date().valueOf() if (currentTs - lastFetchTs > 15000) { refetchData() } }
useInterval自定义Hook代码
const useInterval = (callback, delay = 0) => { const savedCallbackRef = useRef(callback) const intervalIdRef = useRef() useEffect((): void => { savedCallbackRef.current = callback }, [callback]) useEffect(() => { intervalIdRef.current && clearInterval(intervalIdRef.current) if (delay != null) { intervalIdRef.current = setInterval(savedCallbackRef.current, delay) } return () => intervalIdRef.current && clearInterval(intervalIdRef.current) }, [delay]) }
问题原因
核心问题出在useInterval的实现逻辑上:
- 创建定时器时,
setInterval直接传入了savedCallbackRef.current(即组件第一次渲染时生成的回调函数),这个函数通过闭包捕获了当时的loading值(true)。 - 后续组件重新渲染时,虽然
useEffect会将savedCallbackRef.current更新为新的回调函数,但setInterval保存的是最初传入的旧函数引用,不会自动切换到新的回调。因此定时器每次触发时,始终执行旧回调,导致loading值一直停留在最初的true。
修复方案
有两种可行的修复方式,推荐第一种(性能更优,无需频繁销毁重建定时器):
方式一:修改useInterval,动态获取最新回调
将setInterval的回调改为匿名函数,内部调用savedCallbackRef.current,这样每次定时器触发时都会执行最新的回调函数:
const useInterval = (callback, delay = 0) => { const savedCallbackRef = useRef(callback) const intervalIdRef = useRef() useEffect((): void => { savedCallbackRef.current = callback }, [callback]) useEffect(() => { intervalIdRef.current && clearInterval(intervalIdRef.current) if (delay != null) { // 用匿名函数包裹,动态获取最新的回调 intervalIdRef.current = setInterval(() => { savedCallbackRef.current() }, delay) } return () => intervalIdRef.current && clearInterval(intervalIdRef.current) }, [delay]) }
方式二:让useInterval监听callback变化,重建定时器
修改useInterval的第二个useEffect依赖项,加入callback,这样当回调函数变化时,自动销毁旧定时器并创建新定时器,传入最新的回调:
const useInterval = (callback, delay = 0) => { const savedCallbackRef = useRef(callback) const intervalIdRef = useRef() useEffect((): void => { savedCallbackRef.current = callback }, [callback]) useEffect(() => { intervalIdRef.current && clearInterval(intervalIdRef.current) if (delay != null) { intervalIdRef.current = setInterval(savedCallbackRef.current, delay) } return () => intervalIdRef.current && clearInterval(intervalIdRef.current) }, [delay, callback]) // 加入callback作为依赖 }
两种方式都能解决闭包陷阱问题,让回调获取到最新的loading值。方式一避免了频繁销毁和创建定时器,性能更优;方式二更直观,但每次回调变化都会重建定时器。
内容的提问来源于stack exchange,提问作者Cinnabams
相关产品推荐
相关产品推荐

