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)。
相关推荐
智码看视界40 分钟前
老梁聊全栈系列:Vue3核心与组合式API深度解析
javascript·vue.js·ecmascript
原则猫10 小时前
HOOKS 背后机制
前端
码语智行10 小时前
首页导航跳转功能深度解析-系统内和系统外
前端
阿猫的故乡11 小时前
Vue过渡动画从入门到装X:淡入淡出、滑动、列表动画、第三方库全搞定
前端·javascript·vue.js
裕波11 小时前
Vue&ViteConf 2026 将于 7 月 18 日在上海举办,尤雨溪将现场发表主题演讲
vue.js·vite
IManiy11 小时前
总结之Vibe Coding前端骨架
前端
JS菌11 小时前
AI Agent 沙箱双层防护体系:从权限过滤到内核隔离的完整实现
前端·人工智能·后端
Aphasia31112 小时前
从输入URL到页面展示全流程
前端·面试
我叫黑大帅12 小时前
前端如何竖屏固定视口背景
前端·javascript·面试
abcy07121312 小时前
python pandas csv异步后台清洗前端优先返回成功信息
前端·python·pandas