一、 问题背景:什么是"数据偏移"?
在开发无限滚动(Infinite Scroll)或加载更多(Load More)功能时,我们通常使用传统的 page 和 pageSize 进行分页。
场景复现:
- 用户打开列表,加载了第 1 页(1-10条)。
- 在用户阅读这 10 条数据的过程中,系统后台新增了 2 条实时测量记录。
- 用户滑到底部触发加载第 2 页。
- 结果: 此时数据库中的第 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;
四、 总结
- 对于实时性要求不高的管理后台: 前端通过
ID去重是最省成本的选择。 - 对于消息流、测量记录等频繁更新的场景: 强烈建议推动后端同事采用 游标分页 。它不仅解决了数据重复问题,在大数据量下,性能(
WHERE id < x)也远优于传统分页(LIMIT offset, n)。