Next.js中用useState、useEffect每8秒更新滚动位置状态不更新如何解决?
问题原因
- 闭包陷阱:你编写的
useEffect依赖数组为空,仅会在组件首次渲染时执行一次,内部绑定的lazyProgressUpdate函数始终引用首次渲染时的状态值(也就是lazyProgress和lazyProgressMax的初始值0),后续状态更新不会同步到这个闭包内的函数里。 - useState异步更新特性:调用
setLazyProgress(scroll)后,React不会立刻修改当前作用域下的lazyProgress变量,所以后续判断lazyProgress > lazyProgressMax时拿到的还是更新前的旧值,自然不会触发lazyProgressMax的更新。 - 额外风险:Next.js 存在服务端渲染(SSR)阶段,服务端环境不存在
document对象,直接调用会触发报错。
修复方案
直接用计算得到的实时scroll值做判断,同时用函数式状态更新避免依赖闭包旧值,再补充SSR兼容逻辑即可,修改后代码如下:
import { useState, useEffect } from 'react'; const [lazyProgress, setLazyProgress] = useState(0); const [lazyProgressMax, setLazyProgressMax] = useState(0); function lazyProgressUpdate() { // 兼容SSR场景,服务端无document直接返回 if (typeof document === 'undefined') return; const scrollTotal = document.documentElement.scrollTop; const heightWin = document.documentElement.scrollHeight - document.documentElement.clientHeight; // 避免页面内容不满一屏时分母为0报错 if (heightWin === 0) return; const scroll = scrollTotal / heightWin * 100; setLazyProgress(scroll); // 函数式更新直接拿最新的历史最大值做比较,不依赖闭包旧值 setLazyProgressMax(prevMax => Math.max(prevMax, scroll)); } useEffect(() => { const timer = setInterval(() => { lazyProgressUpdate(); }, 8000); return () => clearInterval(timer); // 补充依赖确保函数引用更新 }, [lazyProgressUpdate]); // 若需要打印更新后的状态,单独监听状态变化即可 useEffect(() => { console.log(lazyProgress, lazyProgressMax); }, [lazyProgress, lazyProgressMax]);
可选优化
如果需要滚动时实时更新进度,不需要固定8秒轮询,可以把定时器替换为scroll事件监听,配合节流函数避免高频触发性能问题。
内容的提问来源于stack exchange,提问作者Aadhit Shanmugam
相关产品推荐
相关产品推荐

