Vue3 分页加载避坑指南:如何解决“向下滚动时出现重复数据”的问题?

一、 问题背景:什么是"数据偏移"?

在开发无限滚动(Infinite Scroll)或加载更多(Load More)功能时,我们通常使用传统的 page 和 pageSize 进行分页。

场景复现:

  1. 用户打开列表,加载了第 1 页(1-10条)。
  2. 在用户阅读这 10 条数据的过程中,系统后台新增了 2 条实时测量记录。
  3. 用户滑到底部触发加载第 2 页。
  4. 结果: 此时数据库中的第 11、12 条数据,其实就是原本第 1 页的最后两条。用户会发现列表里出现了重复的卡片。

这种由于数据集动态更新导致的分页错位,在技术上被称为 "数据偏移 (Pagination Drift)"。


二、 解决方案深度对比

针对这个问题,通常有三种主流解决方案,开发者可以根据后端配合程度灵活选择。

方案 原理 优点 缺点
前端 ID 去重 在 push 数据前,利用 Set 或 filter 过滤已存在的 ID。 零后端改动,实现极快。 无法解决"数据漏看"问题(新数据会把旧数据挤到更后的页码)。
游标分页 (Cursor) 不传 page,传最后一条数据的 ID。 行业标准,彻底解决重复和遗漏。 需要后端修改 SQL 逻辑。
时间戳锁定 首次请求记录 now(),后续分页均带上该时间。 保证用户本次浏览的数据集是"静态"的。 无法看到浏览期间产生的新数据。

三、 代码实战:Vue 3 + Element Plus 环境下的实现

方法 1:前端逻辑去重

如果后端暂时无法修改接口,我们可以在 fetchData 函数中通过 Map 或 filter 保证 UI 层的唯一性。

javascript 复制代码
const fetchData = async () => {
  if (loading.value || noMore.value) return;

  try {
    loading.value = true;
    const res = await API.getMeasureFrontList({
      page: pagination.value.page,
      page_size: pagination.value.pageSize,
    });

    if (res.code === 0) {
      const newData = res.data || [];

      // --- 关键去重逻辑 ---
      // 提取当前已有数据的 ID 集合
      const existingIds = new Set(measureList.value.map(item => item.id));
      // 过滤掉已经在列表中存在的 ID
      const uniqueNewData = newData.filter(item => !existingIds.has(item.id));
      
      // 追加去重后的数据
      measureList.value.push(...uniqueNewData);

      // 判断逻辑:以原始返回长度判断是否还有后续页
      if (newData.length < pagination.value.pageSize) {
        noMore.value = true;
      } else {
        pagination.value.page++;
      }
    }
  } catch (err) {
    console.error("加载失败:", err);
  } finally {
    loading.value = false;
  }
};
方法 2:游标分页 (Cursor-based)

这种方法要求后端接口接受一个 last_id 参数。

前端代码调整:

javascript 复制代码
const fetchData = async () => {
  // 获取当前列表最后一条数据的 ID
  const lastItem = measureList.value[measureList.value.length - 1];
  const lastId = lastItem ? lastItem.id : null;

  const res = await API.getMeasureFrontList({
    last_id: lastId, // 传给后端作为查询起点
    page_size: pagination.value.pageSize,
  });

  if (res.code === 0) {
    measureList.value.push(...res.data);
    // ... 其他逻辑
  }
};

后端伪代码 (SQL):

sql 复制代码
-- 传统:SELECT * FROM table ORDER BY id DESC LIMIT 10 OFFSET 10; (会导致重复)
-- 游标:从上次看到的 ID 之后开始取
SELECT * FROM table 
WHERE id < [last_id] 
ORDER BY id DESC 
LIMIT 10;

四、 总结

  1. 对于实时性要求不高的管理后台: 前端通过 ID 去重是最省成本的选择。
  2. 对于消息流、测量记录等频繁更新的场景: 强烈建议推动后端同事采用 游标分页 。它不仅解决了数据重复问题,在大数据量下,性能(WHERE id < x)也远优于传统分页(LIMIT offset, n)。
相关推荐
Java后端的Ai之路9 小时前
06-React-Props
前端·react.js·前端框架
Flynt9 小时前
开源"间谍卫星"说数据全是真的,我把它的免key数据源挨个试了一遍
前端·开源·数据可视化
Csvn10 小时前
渲染管线
前端
程序员大阳10 小时前
副队长CSS教程(33)–圆角与透明
前端·css·透明·圆角
志尊宝10 小时前
Vue3 零基础每日笔记(082):文件上传实战——el-upload 校验、自定义上传、头像预览
前端·javascript·vue.js·笔记·#javascript·#vue.js
鬼手点金11 小时前
opencode-rules 插件
java·开发语言·前端·人工智能·chrome·git·github
柳杉11 小时前
借助 Codex 和可视化工作流,从调研到完整实现,3D 大屏竟然可以这么简单
前端·ai编程·数据可视化
IMPYLH11 小时前
HTML 的 <wbr> 元素
前端·html
Dovis(誓平步青云)11 小时前
常用命令藏得太深?怎么做一个快捷命令坞
运维·前端·自动化·音视频·prometheus
IT_陈寒12 小时前
Vite的这个问题坑了我三天,原来如此解决!
前端·人工智能·后端