分页懒加载与前端并发控制

列表从 20 条增长到 2000 条以后,“一次全拿”会越来越慢。分页本身不难,难的是刷新、追加、搜索和快速切换同时发生时,列表仍然不重、不乱、不倒退。
1. 基础分页状态
const rows = ref([])const pageNum = ref(1)const pageSize = 20const total = ref(0)const loading = ref(false)const loadingMore = ref(false)const hasMore = computed(() => rows.value.length < total.value)hasMore 根据实际已加载数量判断,比单纯判断“这一页是否满 20 条”更可靠。
2. 下拉刷新和触底追加
刷新永远请求第一页并替换旧列表;追加请求下一页并合并:
const targetPage = reset ? 1 : pageNum.value + 1const response = await fetchList({ pageNum: targetPage, pageSize })rows.value = reset ? response.rows : mergeUnique(rows.value, response.rows)pageNum.value = targetPage只有请求成功后才更新 pageNum。如果先加页码再请求,失败重试会跳过一页。
3. 请求锁
if (loading.value || loadingMore.value) returnif (!reset && !hasMore.value) return触底事件可能在短时间触发多次,锁可以避免同一页被重复追加。按钮 disabled 只是表现层,方法内部仍要判断。
4. 去重
时间倒序列表在分页期间可能插入新记录,导致第二页包含第一页末尾的数据。用稳定主键去重:
function mergeUnique(current, incoming) { const map = new Map() current.concat(incoming).forEach(item => map.set(String(item.id), item)) return [...map.values()]}如果没有单一 ID,可以使用业务组合键,但要保证稳定。
5. 重新排序
消息列表合并后按 sendTime 倒序,异常列表按 createTime 倒序。日期字符串先做跨端兼容解析,解析失败的记录放在末尾。排序只影响展示,不修改分页请求的服务端排序口径。
6. 查询竞态
用户从“全部”快速切到“配送中”,旧请求可能晚返回。使用版本号:
let queryVersion = 0async function query() { const version = ++queryVersion const result = await fetchList(currentParams.value) if (version !== queryVersion) return rows.value = result.rows}或者使用 AbortController/小程序 request task 取消旧请求。取消适合节省资源,版本号适合保证最终一致,两者可以组合。
7. 页面生命周期
一级页面常在 onShow 刷新最新数据,详情返回时列表即可更新;但不要在 onShow、onMounted 和下拉刷新中同时无条件请求。明确唯一首次加载入口,再由用户动作触发其他刷新。
8. 空态与底部提示
列表需要区分首次加载、真正为空、加载更多、已加载全部和失败重试。一个永远显示“暂无数据”的页面,会把网络失败伪装成业务空数据。
9. 本篇小结
懒加载的目标不是把 2000 条切成 100 次请求,而是让用户先看到最新内容,并且往下找时自然接上历史。列表如果会重复、跳页和回滚,再勤快的“加载更多”也只是多制造一点混乱。
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!





























