人人都会AI编程

长列表懒加载与分页加载

更新时间:2026-07-09

当列表数据量达到几百上千条甚至更多时,一次性把所有数据加载到页面并渲染,会导致两个严重问题:

  • 首屏加载慢:接口返回大量数据,传输时间长,用户看到白屏或加载动画很久。
  • 页面卡顿:大量 DOM 节点同时存在,滚动、点击等交互都可能出现明显延迟。

解决这个问题的思路很简单:别一次性加载所有数据,用户看到哪,就加载到哪。根据用户交互方式的不同,派生出两种主流模式:分页加载懒加载

分页加载:用页码控制数据范围

分页是最传统也最稳妥的长列表处理方案。前端告诉后端“我要第几页、每页多少条”,后端返回对应的数据切片和总条数。

// 典型的分页请求参数
{
  page: 1,        // 当前页码
  pageSize: 20    // 每页条数
}

前端通常搭配一个分页器组件,显示页码、总条数,并提供“上一页”“下一页”跳转。用户主动翻页时,触发新的请求,替换当前列表数据。

实现要点

  • 后端必须提供明确的“总数”字段,前端才能计算出总页数。
  • 搜索、筛选条件变化时,需要将页码重置为第 1 页,避免出现“筛选后数据只有 3 条,但页码还停留在第 5 页”的空页面。
  • 分页模式天然支持精确定位——用户可以直接跳到第 8 页,或通过 URL 参数保存当前页码,刷新页面后依然回到同一位置。

适用场景

  • 后台管理系统的数据表格(需要精确查看某一页,支持批量操作)。
  • 搜索引擎结果(用户习惯翻页)。
  • 数据总量可控,且前端不需要“无限滚动”的沉浸式浏览。

懒加载:滚动到底部自动加载下一页

懒加载(也叫无限滚动)不再依赖页码按钮,而是监听滚动条位置,当用户滚动到接近列表底部时,自动触发请求加载下一批数据,并追加到现有列表中。

// 滚动加载的核心逻辑示意
const list = ref([])
const page = ref(1)
const loading = ref(false)
const finished = ref(false) // 是否没有更多数据了

function onScroll() {
  const container = document.querySelector('.container')
  // 判断是否触底:距离底部小于阈值且未在加载中
  if (container.scrollHeight - container.scrollTop - container.clientHeight < 50 
      && !loading.value && !finished.value) {
    loadMore()
  }
}

async function loadMore() {
  loading.value = true
  const res = await fetchList({ page: page.value, pageSize: 20 })
  list.value.push(...res.data)
  page.value++
  if (res.data.length < 20) finished.value = true
  loading.value = false
}

实现要点

  • 加载状态提示:底部显示“加载中…”,网络慢时给用户明确反馈,避免用户以为已经没了。
  • 结束状态:当接口返回数据少于 pageSize,或者后端明确返回“无更多数据”标识时,停止继续请求,底部显示“没有更多了”。
  • 防止重复加载:用 loading 标记位锁住请求,避免用户快速滚动触发多次相同请求。
  • 数据追加方式:新数据一定要 push 到现有数组末尾,而不是替换整个数组,否则滚动位置会丢失。

适用场景

  • 信息流型页面(如朋友圈、动态列表、商品瀑布流)。
  • 移动端 H5,用户更习惯滑动浏览而不是点击翻页。
  • 当内容沉浸式浏览为主,无需全量检索某一特定条目。

分页 vs 懒加载:怎么选?

两者并不互斥,关键看你的产品体验“以浏览为主”还是“以定位操作为主”:

  • 分页:用户目标明确,需要快速定位到具体页(如“找到第 100 条订单”),或需要跨页批量选择。它的代价是操作有中断感——点一下翻页按钮,等请求,再看数据。
  • 懒加载:用户体验流畅,手指一滑内容自然出现。弊端是用户无法直接跳到第 N 页,如果数据量极大(几万条),越滑到后面页面 DOM 节点越多,仍然可能卡顿。

懒加载的致命缺陷:DOM 节点无限增长

即使我们用懒加载避免了“一次性全量请求”,随着用户不断下滑,list 数组持续增长,页面上的 DOM 节点也随着增多。当列表滚到几千条时,即使每条只渲染一个简单的 div,几千个 DOM 节点也会让滚动越来越卡。

这就是懒加载和虚拟列表需要结合使用的场景。虚拟列表不关心你分页还是懒加载,它只解决“大量 DOM 节点同时存在”的问题。通常是懒加载负责“慢慢拉数据”,虚拟列表负责“只渲染可视区域内的那十几个 DOM 节点”,两者配合才能应对真正海量的数据浏览。虚拟列表的实现原理会在后续专门展开。

实战中的常见坑

  • 分页加载时忘记重置页码:在搜索、筛选条件变化后,要从第 1 页重新请求,否则会出现“筛完数据只剩 2 条,但前端请求了第 3 页”的空结果。
  • 懒加载的竞态:用户快速切换筛选条件,前一个请求还未返回,后一个请求已经发出,可能导致旧数据覆盖新数据。解决方案是请求时加入一个递增标记,返回时比对标记是否一致,或用请求取消。
  • 频繁触发滚动事件scroll 事件需要在绑定函数中至少做节流处理(每 100ms 检查一次),否则在移动端滚动时每秒可能触发上百次回调,性能浪费严重。
  • 列表加载更多后页面跳变:如果是图片列表,图片加载后撑开高度,可能让页面突然上移。给图片容器预留固定宽高比(aspect-ratio)或者占位高度可以避免。