在setInterval中传递箭头函数与函数引用的差异及问题排查
setInterval传递箭头函数与函数引用的差异分析
问题
请问在setInterval中传递箭头函数和传递函数引用是否存在差异?即setInterval(callbackRef.current, 5000)与setInterval(() => callbackRef.current(), 5000)这两种写法有何不同?为何我的代码中只有第二种写法生效,第一种写法会在回调函数中输出旧值?
上下文代码
const MyPage = (callback, delay): void => { const savedCallbackRef = useRef(callback) useEffect((): void => { savedCallbackRef.current = callback }, [callback]) useEffect(() => { if (delay != null) { const id = setInterval(savedCallbackRef.current, delay) return () => clearInterval(id) } }, [delay]) }
更新示例
useInterval 钩子
const useInterval = (callback, delay): void => { const savedCallbackRef = useRef(callback) useEffect((): void => { savedCallbackRef.current = callback }, [callback]) useEffect(() => { if (delay != null) { const id = setInterval(savedCallbackRef.current, delay) return () => clearInterval(id) } }, [delay]) }
组件使用
const MyApp = ({ loading }) => { useInterval(() => { if (!loading) { // do something } }, 500) }
差异分析与原因
核心差异:函数引用的捕获时机
- 直接传递函数引用:
setInterval(savedCallbackRef.current, delay)会在useEffect执行的瞬间,捕获当前savedCallbackRef.current指向的函数引用,并将这个固定的引用绑定给定时器。后续即使savedCallbackRef.current被更新为新的函数,定时器依然只会调用最初捕获的旧函数。 - 箭头函数包裹:
setInterval(() => savedCallbackRef.current(), delay)中,定时器捕获的是箭头函数的引用。每次定时器触发时,才会动态读取savedCallbackRef.current的最新值,并调用这个最新的函数。
结合你的代码逻辑解释
在useInterval钩子中:
- 第一个
useEffect会在传入的callback更新时,同步将savedCallbackRef.current替换为最新的回调函数。 - 第二个
useEffect仅依赖delay,只有当delay变化时才会重新执行,创建新的定时器。
当MyApp中的loading状态改变时,传给useInterval的回调会变成一个新的箭头函数(组件每次渲染都会生成新的箭头函数),此时第一个useEffect会更新savedCallbackRef.current为新函数。但第二个useEffect因delay未变化不会重新执行,旧定时器依然持有最初的回调引用,导致执行时使用旧的loading逻辑;而箭头函数包裹的写法,每次触发都会取最新的savedCallbackRef.current,自然能拿到包含最新loading状态的回调。
修正方案
将第二个useEffect中的setInterval改为箭头函数包裹的形式即可:
useEffect(() => { if (delay != null) { const id = setInterval(() => savedCallbackRef.current(), delay) return () => clearInterval(id) } }, [delay])
内容的提问来源于stack exchange,提问作者Cinnabams
相关产品推荐
相关产品推荐

