大列表与虚拟化
大列表与虚拟化 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。
学习目标
读完你能解释列表卡顿卡在 DOM 数量而非数据数量:实现一个最小虚拟列表(可视窗口 + 撑高容器 + 偏移计算),对比分页与虚拟化的适用边界,并验证滚动流畅与键盘定位正确。
问题场景
审计日志页要一次展示 2 万条记录。整表渲染:首屏数秒,滚动掉帧。第一反应是分页——但用户要 Ctrl+F 全文搜索和连续滚动浏览,分页把体验切断了。问题的本质:数据 2 万条不贵,贵的是 2 万个 DOM 节点。浏览器一次只需要看得见的几十条,其余都是白渲染。
核心解释
虚拟化的三个零件:
function VirtualList({ items, rowHeight, height }) {
const [scrollTop, setScrollTop] = useState(0);
const total = items.length * rowHeight; // 1. 撑高:真实总高度
const start = Math.max(0, Math.floor(scrollTop / rowHeight) - 3); // 2. 可视窗口起点(含缓冲 3 行)
const count = Math.ceil(height / rowHeight) + 6;
const slice = items.slice(start, start + count);
return (
<div style={{ height, overflow: "auto" }} onScroll={(e) => setScrollTop(e.currentTarget.scrollTop)}>
<div style={{ height: total, position: "relative" }}>
<div style={{ position: "absolute", top: start * rowHeight, left: 0, right: 0 }}>
{slice.map((it) => (
<div key={it.id} style={{ height: rowHeight }}>{it.text}</div>
))}
</div>
</div>
</div>
);
}
无论数据多少,DOM 里永远只有「可视区 + 缓冲」几十行;滚动只是窗口起点在变。代价有两条:随机访问(跳到第 15000 条要先算偏移再滚过去)和变高行(等高实现最简单,变高行要维护位置索引,复杂度上一个台阶)。
分页与虚拟化的选择:用户要「连续浏览 + 全文检索 + 快速定位」,虚拟化;用户按「页」的心智消费数据、后端也按页出接口,分页;两者可叠加(虚拟化 + 增量加载)。键盘定位是虚拟化的经典坑:Ctrl+F、Tab、屏幕阅读器都「看不见」窗口外的行,选中项必须主动滚进窗口:
useEffect(() => {
const el = document.getElementById("row-" + selectedId);
if (el) el.scrollIntoView({ block: "nearest" });
}, [selectedId]);
示例拆解
实验区 2 万条日志两种渲染对照:
- 「虚拟列表」开关:开启时页面显示「本次渲染行数」约 30 行;关闭则整表渲染 2 万行(第一次切换会明显卡几秒,这正是要演示的);
- 「跳转到第 N 条」:虚拟模式下瞬间定位并把该行滚入视野中央;整表模式跳转代价极高(输入框被禁用,先体验再开虚拟);
- 键盘:点击滚动容器获得焦点后,↑/↓ 移动选中行,选中接近边缘时窗口自动跟随;
- 验证:对比两种渲染的行数与滚动手感;键盘把选中走到第 19000 条,确认它滚进视野。
动手任务
打开本课实验区:
- 把缓冲从 3 行改成 0,快速滚动观察边缘白条闪现,再改回——理解缓冲区的作用。
- 把 rowHeight 改成 48,确认偏移计算仍然正确(体会公式里 rowHeight 是唯一耦合点)。
- 给「跳转」加键盘支持:输入条数后按 Enter 跳转并把该行选中。
常见错误
- 忘了撑高容器:没有 total 高度占位,滚动条长度错误,滚到一半就「到底」。
- 窗口计算漏了缓冲:快速滚动时边缘频繁出现尚未渲染的白条;缓冲 3-5 行兜底。
- 键盘或查找功能没做滚动跟随:选中了窗口外的行,用户看着「没反应」。
- 行组件内部又渲染大子树:窗口里只有几十行,但单行成本过高,窗口行数 × 单行成本重新变大;行本身也要轻。
检查与复盘
自查清单:
- 能说清撑高、窗口、缓冲三个零件各自的作用
- 2 万条数据下,渲染行数与滚动流畅度验证过
- 键盘选中窗口外的行能正确滚入视野
参考资料
- react-window 文档 —— 生产级虚拟列表的 API 形态
- TanStack Virtual 文档 —— 变高行与高级场景
- MDN:Element.scrollIntoView —— 定位行滚入视野
读完这一课了?
标记完成后,工作台会安排接下来的学习。