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

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

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

场景复现:

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

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


二、 解决方案深度对比

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

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

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

方法 1:前端逻辑去重

如果后端暂时无法修改接口,我们可以在 fetchData 函数中通过 Mapfilter 保证 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)。
相关推荐
樊小肆1 小时前
# 你还在等DeepSeek官方 agent Harness‌? 来试试 DeepSeeker-Code吧
前端·人工智能·后端
樊小肆1 小时前
2568 万 token 才花 2 块 2:聊聊 DeepSeeker-Code 怎么吃满上下文缓存
前端·人工智能·后端
JarvanMo1 小时前
Flutter 3.47: material/cupertino终于解耦了
前端
郭邯2 小时前
一次「字符洁癖」引发的思考:我用 AI 写了个全角半角转换工具
前端
想要成为糕糕手2 小时前
🚀 在浏览器里跑 DeepSeek-R1?WebGPU 端侧推理实战(五)—— 中断、重置、缓存与流式生成
前端·react.js·llm
GuWenyue2 小时前
后端接口又双叒没写好?3 步搭建前端 Mock,从此告别"傻等后端"
前端·mocha
海兰2 小时前
【数据采集】开源的 Web 数据采集与处理Firecrawl(一)
前端·开源
vivo互联网技术2 小时前
从一键检测到 AI 修复:我们如何把无障碍检查做进研发流程
前端·人工智能
liuxiaocheng2 小时前
聊聊 Vercel AI SDK 的流式协议:前后端到底是怎么"边想边说"的
前端·后端
胡萝卜术2 小时前
在浏览器中跑 DeepSeek-R1:WebGPU 推理全流程深度解析
前端·javascript·面试