React分页切换setState更新异常 新旧数据交替显示问题
在React中实现URL驱动的分页功能时,从第2页跳转到第3页会出现两页数据交替闪烁的问题,点击跳转后虽然手动清空了帖子列表,旧页数据仍会短暂出现,加载状态无法保持到新数据完全拉取完成,异常现象如下:
问题根因
useEffect未传入依赖数组,组件每次渲染都会重复执行数据拉取逻辑,产生大量冗余请求。- 缺少请求竞态处理:点击跳转触发第3页请求时,之前发起的第2页请求仍处于待响应状态,手动执行
setPosts([])清空列表后,晚到的第2页请求响应会执行setPosts(第2页数据),把列表重新填为旧数据,等第3页请求响应后又会覆盖为新数据,最终出现两页数据交替显示的现象。 - 状态更新逻辑分散:把
setLoading(true)、setPosts([])写在Link组件的onClick事件中,和路由参数变化触发的数据拉取逻辑不同步,很容易出现loading状态提前终止的问题。 - 直接修改只读的props值:代码中直接给
match.params.page赋值,违反React props只读的原则,可能引发不可预期的异常。
修复方案
核心修复逻辑:
- 给useEffect添加正确的依赖项,仅在页码变化时触发数据拉取
- 将loading状态设置、列表清空逻辑统一收归到useEffect中,和数据拉取逻辑保持同步
- 添加竞态标记,仅允许最新一次发起的请求更新组件状态,丢弃过期请求的响应结果
- 不直接修改props,使用本地变量处理页码默认值
修复后的完整代码如下:
const Daily = ({match}) => { const [posts, setPosts] = useState([]); const [numberOfPages, setNumberOfPages] = useState(0); const [loading, setLoading] = useState(true); const perPageItems = 8; useEffect(() => { // 竞态标记,标识当前请求是否过期 let isStale = false; // 进入页面/页码变化时,先设置加载状态、清空旧列表 setLoading(true); setPosts([]); const totalPost = () => { getPostsCount() .then(data => { if (!isStale) { setNumberOfPages(Math.ceil(data/perPageItems)) } }) } const loadIndexPosts = (a, b) => { getPostsByIndex(a, b) .then(data => { // 如果是过期请求的返回,直接丢弃不更新状态 if (isStale) return; if (data.error) { console.log(data.error); } else { setPosts(data); } setLoading(false); }) }; totalPost() // 不修改原props,用本地变量处理页码默认值 const currentPage = match.params.page ? parseInt(match.params.page) : 1; const startIdx = (currentPage - 1) * perPageItems; loadIndexPosts(startIdx, perPageItems) // 清理函数:组件卸载/新请求触发时,标记旧请求为过期 return () => { isStale = true; } }, [match.params.page]) // 仅在页码参数变化时重新执行 effect return ( <div> <div className="mt-5 daily-card-feed"> {loading && <ThreeDotsWave/>} {posts.map((post, index) => { return ( <div key={index}> <DailyCard post={post}/> </div> ) })} </div> <div className="h4 pb-5 pull-right mr-5"> {/* 移除onClick里的手动状态更新,状态逻辑统一由effect管理 */} {match.params.page == 1 ? <Link to={`/daily/page/2`}>Older Post -></Link> : <Link to={`/daily/page/${parseInt(match.params.page)+1}`}>Older Post -></Link> } </div> </div> ) }
修复后,旧请求返回时会因为
isStale标记为true直接被丢弃,不会再触发状态更新覆盖新页面的加载状态,loading状态会一直保持到最新请求返回后才会关闭,从根本上解决数据交替闪烁的问题。
内容的提问来源于stack exchange,提问作者user16341689
相关产品推荐
相关产品推荐

