虚拟列表通过请求、数据管理与渲染协同优化解决大数据量卡顿问题:后端需支持分页/游标/streaming,前端用ref存数据、缓存高度、固定容器高度+占位div撑高,仅渲染可视区域±2条,配合节流滚动监听与骨架屏加载态。

接口返回大量数据时,直接渲染会导致页面卡顿甚至崩溃。虚拟列表不是“等接口回来再处理”,而是从请求、数据管理到渲染全程协同优化。核心是:不把全部数据塞进 DOM,也不让 JS 等待完整响应才开始滚动体验。
接口设计要支持流式或分段获取
后端应提供分页、游标(cursor)或范围查询能力,避免一次性返回 10 万条。即使要做“全量加载”,也建议用 streaming JSON 或分块响应(如每次 2000 条 + continue token),前端可边收边存,不阻塞 UI。
- 优先用 cursor 分页(比 offset 更稳定,尤其数据实时变动时)
- 接口响应头中带上
Total-Count或X-Total-Count,用于计算占位容器总高度 - 若必须全量加载,可在 fetch 后用
response.json()前先做轻量校验(如检查 data 字段是否存在),避免空数组或错误结构导致渲染异常
数据预加载与内存管理
拿到数据后别急着塞进 state。虚拟列表依赖快速索引访问,建议将原始数组转为只读引用,并缓存项高度(固定高最简单;动态高可用 ResizeObserver 首次测量后存 map)。
- 用
Object.freeze(data)或const items = [...data]避免意外修改 - 不把整个数组存在 React state 里(尤其用
useState),改用useRef存原始数据,state 只管当前滚动位置和可视范围 - 超过 5 万条时,考虑用
WeakMap缓存已测高度,避免重复 DOM 查询
滚动容器与渲染逻辑解耦
虚拟列表的容器必须有固定高度、overflow-y: auto,且内部用一个绝对定位的占位 <div> 撑起总高度(<code>height: ${itemHeight * total})。真实内容只渲染约“可视区域 ±2 条”的片段。
- 监听
scroll事件前,先用throttle(32ms 或 requestIdleCallback)防高频触发 - 计算起始索引用
Math.floor(scrollTop / itemHeight),结束索引加缓冲(如 +5),避免滚动抖动 - 用
DocumentFragment批量插入,或 React 中用key精确控制重用,不依赖 index 当 key(尤其数据可能增删时)
加载态与边界处理不能省
用户滚动到底部时,如果数据还没全到,得给明确反馈;顶部刷新、搜索过滤后,也要重置滚动位置并重新计算可视范围。
- 接口还在请求中,但用户已滚到未加载区域?显示 skeleton 占位,不报错也不留白
- 下拉刷新时,清空 ref 数据、重置 scrollTop,再触发新请求
- 搜索过滤后,更新 total 和 height 缓存,并强制触发一次 scroll 事件来刷新可视项











