React状态已更新,但testFunc中console.log始终返回初始值0求解释
问题原因与解决方案
核心原因
这是闭包捕获旧状态结合useEffect依赖缺失导致的问题,拆解来看:
- 闭包捕获特性:
useCallback创建的testFunc会捕获当前作用域的test值。组件首次渲染时test是0,此时生成的testFunc里的test就固定是0;只有当test变化时,useCallback才会生成新的函数,捕获新的test值。 - useEffect依赖问题:你的
useEffect依赖是空数组[],只会在组件首次渲染时执行一次,给window绑定的scroll事件监听是初始版本的testFunc——也就是那个捕获了test=0的函数。后续test更新时,虽然会生成新的testFunc,但useEffect不会重新执行,所以scroll事件始终调用旧的函数,console.log(test)自然一直输出0。 - 为什么状态能正常更新?因为你用了函数式更新
setTest(prev => prev + 1),这种方式不依赖当前作用域的test值,而是直接取React状态的最新快照,所以状态可以正常累加。
解决方案
方案一:给useEffect添加正确依赖,自动更新事件绑定
让useEffect在testFunc更新时重新绑定scroll事件,同时添加清理函数防止内存泄漏:
'use client' import React, { useCallback, useEffect, useState } from 'react' type Props = {} const TestPage = (props: Props) => { const [test, setTest] = useState<number>(0) const testFunc = useCallback(() => { console.log(test) setTest(prev => prev + 1) }, [test]) const handleClick = () => console.log(test) useEffect(() => { window.addEventListener('scroll', testFunc) // 清理函数:组件卸载或依赖更新时移除旧监听 return () => { window.removeEventListener('scroll', testFunc) } }, [testFunc]) // 依赖testFunc,test变化时重新绑定 return ( <> <div className='h-screen bg-black' onClick={handleClick}>TestPage</div> <div className='h-screen bg-black' onClick={handleClick}>TestPage</div> <div className='h-screen bg-black' onClick={handleClick}>TestPage</div> <div className='h-screen bg-black' onClick={handleClick}>TestPage</div> <div className='h-screen bg-black' onClick={handleClick}>TestPage</div> </> ) } export default TestPage
方案二:用useRef保存最新函数,避免频繁绑定/解绑
如果不想每次test变化都重新绑定事件,可以用useRef存储最新的testFunc,让scroll事件始终调用最新版本:
'use client' import React, { useCallback, useEffect, useRef, useState } from 'react' type Props = {} const TestPage = (props: Props) => { const [test, setTest] = useState<number>(0) const testFuncRef = useRef<() => void>(() => {}) const testFunc = useCallback(() => { console.log(test) setTest(prev => prev + 1) }, [test]) // 每次testFunc更新时,同步到ref useEffect(() => { testFuncRef.current = testFunc }, [testFunc]) const handleClick = () => console.log(test) useEffect(() => { const handleScroll = () => { testFuncRef.current() } window.addEventListener('scroll', handleScroll) return () => { window.removeEventListener('scroll', handleScroll) } }, []) return ( <> <div className='h-screen bg-black' onClick={handleClick}>TestPage</div> <div className='h-screen bg-black' onClick={handleClick}>TestPage</div> <div className='h-screen bg-black' onClick={handleClick}>TestPage</div> <div className='h-screen bg-black' onClick={handleClick}>TestPage</div> <div className='h-screen bg-black' onClick={handleClick}>TestPage</div> </> ) } export default TestPage
方案三:简化逻辑,直接在事件处理中更新状态
如果你的需求只是滚动时更新状态,不需要在滚动事件里直接访问当前test值,可以去掉useCallback,直接用函数式更新,同时单独监听test变化来打印:
'use client' import React, { useEffect, useState } from 'react' type Props = {} const TestPage = (props: Props) => { const [test, setTest] = useState<number>(0) // 单独监听test变化打印 useEffect(() => { console.log(test) }, [test]) const handleClick = () => console.log(test) useEffect(() => { const handleScroll = () => { setTest(prev => prev + 1) } window.addEventListener('scroll', handleScroll) return () => { window.removeEventListener('scroll', handleScroll) } }, []) return ( <> <div className='h-screen bg-black' onClick={handleClick}>TestPage</div> <div className='h-screen bg-black' onClick={handleClick}>TestPage</div> <div className='h-screen bg-black' onClick={handleClick}>TestPage</div> <div className='h-screen bg-black' onClick={handleClick}>TestPage</div> <div className='h-screen bg-black' onClick={handleClick}>TestPage</div> </> ) } export default TestPage
内容的提问来源于stack exchange,提问作者William.D
相关产品推荐
相关产品推荐

