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)。
相关推荐
一蓑烟雨,一任平生23 分钟前
鸿蒙H5调试方法
前端·华为·h5·harmonyos
Canace36 分钟前
使用大模型来维护知识库
前端·人工智能
HashTang36 分钟前
用自然语言驱动的开源 3D 建筑设计编辑器-Aedifex
前端·github·ai编程
0vvv01 小时前
2026-NCTF-web-N-RustPICA
前端·ctf
前进的李工1 小时前
MySQL角色管理:权限控制全攻略
前端·javascript·数据库·mysql
芯智工坊1 小时前
第13章 Mosquitto监控与日志管理
前端·网络·人工智能·mqtt·开源
洒满阳光的庄园2 小时前
Electron 桌面端打包流程说明
前端·javascript·electron
Jagger_2 小时前
模型能力边界外扩时,工作到底在怎样被重做?
前端
SuperEugene2 小时前
前端通用基础组件设计:按钮/输入框/弹窗,统一设计标准|组件化设计基础篇
前端·javascript·vue.js·架构
Jagger_2 小时前
# 模型边界往外推的时候,我最怕的不是学不会,是没人听我解释
前端