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)。
相关推荐
IT_陈寒6 分钟前
Redis雪崩把我坑惨了,这是5年经验的教训
前端·人工智能·后端
SameX16 分钟前
HarmonyOS ArkUI List 的侧滑删除 用 ListItem 的 swipeAction + endIcon 的做法
前端
usdoc文档预览3 小时前
USDOC系列之PPT文件在线编辑-VUE前端实现在线浏览器在线编辑PPT文档
前端·vue.js·编辑器·office365·ppt在线编辑·office文件在线编辑·文件在线编辑
Java小卷3 小时前
面向 schema 编程,轻松搞定 Element 虚拟化表格复杂应用场景
vue.js·低代码·element
用户2705077725243 小时前
Agent 调工具卡住?3 种超时与取消策略实测对比
前端
LuHang4 小时前
我一行代码没写,把领导派的活做成了公司内部系统😭
前端
明月_清风4 小时前
Meta 开源了一个 AI Native 组件库:Astryx,150+ React 组件,专为 AI Agent 而设计
前端·人工智能·ai编程
仨孩子的爹4 小时前
Lodop、hiprint 与 vue-print-designer:打印控件选型对比
vue.js
用户850869276154 小时前
秒杀系统中 Shell 脚本并发控制 Bug:究竟哪里出了
前端
炫酷的大高个7 小时前
6 亿播放的闪身步,我用前端算出 15 倍重心差
前端·javascript