You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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只读的原则,可能引发不可预期的异常。
修复方案

核心修复逻辑:

  1. 给useEffect添加正确的依赖项,仅在页码变化时触发数据拉取
  2. 将loading状态设置、列表清空逻辑统一收归到useEffect中,和数据拉取逻辑保持同步
  3. 添加竞态标记,仅允许最新一次发起的请求更新组件状态,丢弃过期请求的响应结果
  4. 不直接修改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 -&gt;</Link> :
                    <Link to={`/daily/page/${parseInt(match.params.page)+1}`}>Older Post -&gt;</Link>
                }
            </div>
        </div>
    )
}

修复后,旧请求返回时会因为isStale标记为true直接被丢弃,不会再触发状态更新覆盖新页面的加载状态,loading状态会一直保持到最新请求返回后才会关闭,从根本上解决数据交替闪烁的问题。

内容的提问来源于stack exchange,提问作者user16341689

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.06 16:15:42