React中useEffect内useState获取旧值问题求助
问题原因
你的问题出在闭包陷阱上:useEffect的依赖数组是空的,这意味着onScrollIntentTriggerFn只在组件首次渲染时创建一次,它捕获的isScrollReached始终是初始值false。哪怕之后通过setIsScrollReached(true)更新了状态,这个函数里的isScrollReached依然是旧值,所以滚动回顶部时的判断条件永远不成立。
解决方案
下面提供几种可行的修复方式:
方法1:将状态加入useEffect依赖数组
把isScrollReached添加到useEffect的依赖列表中,这样每次状态变化时,都会重新创建onScrollIntentTriggerFn并绑定新的滚动事件,确保函数能拿到最新的状态值。
const [isScrollReached, setIsScrollReached] = useState(false); const onScrollIntentTriggerFn = event => { const scrollRatio = window.pageYOffset / document.body.offsetHeight; if (scrollRatio > 0.6) { setIsScrollReached(true); } if (scrollRatio < 0.05 && isScrollReached) { console.log('User scroll to 60% and came back to top of the screen'); } }; useEffect(() => { window.addEventListener('scroll', onScrollIntentTriggerFn); return () => { window.removeEventListener('scroll', onScrollIntentTriggerFn); }; }, [isScrollReached]); // 添加状态到依赖数组
方法2:使用useRef保存最新状态
利用useRef的current属性可变且不受闭包影响的特性,实时同步状态值,在滚动函数里直接访问ref.current获取最新状态。
const [isScrollReached, setIsScrollReached] = useState(false); const scrollReachedRef = useRef(false); // 同步状态到ref useEffect(() => { scrollReachedRef.current = isScrollReached; }, [isScrollReached]); const onScrollIntentTriggerFn = event => { const scrollRatio = window.pageYOffset / document.body.offsetHeight; if (scrollRatio > 0.6) { setIsScrollReached(true); } // 访问ref.current获取最新状态 if (scrollRatio < 0.05 && scrollReachedRef.current) { console.log('User scroll to 60% and came back to top of the screen'); } }; useEffect(() => { window.addEventListener('scroll', onScrollIntentTriggerFn); return () => { window.removeEventListener('scroll', onScrollIntentTriggerFn); }; }, []); // 依赖数组保持为空
方法3:使用useCallback包裹滚动函数
结合useCallback缓存函数,当依赖变化时重新生成函数,确保函数能捕获最新的状态。
const [isScrollReached, setIsScrollReached] = useState(false); // 使用useCallback包裹,依赖isScrollReached const onScrollIntentTriggerFn = useCallback(event => { const scrollRatio = window.pageYOffset / document.body.offsetHeight; if (scrollRatio > 0.6) { setIsScrollReached(true); } if (scrollRatio < 0.05 && isScrollReached) { console.log('User scroll to 60% and came back to top of the screen'); } }, [isScrollReached]); useEffect(() => { window.addEventListener('scroll', onScrollIntentTriggerFn); return () => { window.removeEventListener('scroll', onScrollIntentTriggerFn); }; }, [onScrollIntentTriggerFn]); // 依赖useCallback返回的函数
补充说明
- 方法1和方法3逻辑类似,都是通过更新函数引用,让滚动事件监听最新状态;方法2则是绕过闭包,直接访问可变的ref值。
- 推荐优先使用方法1或方法2,逻辑更直观易维护。
内容的提问来源于stack exchange,提问作者SRIKANT MAHARANA
相关产品推荐
相关产品推荐

