其他
长列表/表格渲染成百上千项导致滚动卡顿或首屏慢时…
在 React 或网页中渲染几百到几千项的列表/网格并出现性能问题时,采用窗口化虚拟列表。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 35/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 38/100 ×0.15
适用场景
长列表/表格渲染成百上千项导致滚动卡顿或特定
解决思路
- 用 `react-window` 或 `react-virtualized` 在可滚动容器中只渲染当前可见项,而不是一次渲染整个列表。
- 列表项等高的用 `FixedSizeList`,高度不等的用 `VariableSizeList`。
- 需要随滚动增量加载数据时,用 `react-window-infinite-loader`。
- 更简单的场景且不需要完整虚拟化时,考虑用 CSS `content-visibility: auto`。
- 实现虚拟列表的核心做法是:维护一个窗口,并随着用户滚动移动这个窗口。
- 在 `react-virtualized` 的窗口化实现中,用一个相对定位的小容器 DOM 元素(例如 `<ul>`)作为 window。
- 在容器外放置一个用于滚动的大 DOM 元素。
- 把子元素绝对定位在容器内,并设置它们的 `top`、`left`、`width`、`height` 样式。
- 只渲染用户可见的项;在用户滚动时 fetch/display 更多项,同时卸载之前的项并用新项替换。
- 如果要从 `react-virtualized` 换成更小替代品,用 `react-window`;它更小、更快、更支持 tree-shaking。
- 用 `react-window` 替代 `react-virtualized` 时,可预期减少约 20-30KB(gzipped)体积。
- 两者 API 类似;有差异时,`react-window` 通常更简单。
- 用 `react-window` 的 List 组件渲染 windowed 行列表,只显示可见行;等高等用 `FixedSizeList`,不等高用 `VariableSizeList`。
- 注意 List 内部使用 Grid 渲染行,并把 props 转发给内部 Grid。
- 用 React 普通方式渲染列表时,创建 `Row` 组件接收 `index` 和 `style`,按 `index` 设置奇偶 `className`,并渲染 `itemsArray[index].name`。
- 用 `react-window` 的 `FixedSizeList` 时,传入 `width`、`height`、`itemCount`、`itemSize`,并把行渲染函数 `Row` 作为 child。
- 需要表格数据时用 Grid,在垂直和水平两个轴向上虚拟化;等宽等高用 `FixedSizeGrid`,不等用 `VariableSizeGrid`。
- Grid 只渲染根据当前水平/垂直滚动位置填满自身所需的单元。
- 使用 `FixedSizeGrid` 时,传入 `columnCount`、`columnWidth`、`height`、`rowCount`、`rowHeight`、`width`,并把 `Cell` 作为 child。
- `Cell` 接收 `columnIndex`、`rowIndex`、`style`,设置奇偶 `className`,并渲染 `itemsArray[rowIndex][columnIndex].name`。
- 用 `react-window-infinite-loader` 把大数据集拆成块,在滚动进入视图时加载。
- 用 `InfiniteLoader` 包裹 Grid:传入 `isItemLoaded`、`loadMoreItems`、`itemCount`;child 接收 `onItemsRendered` 和 `ref`,把 `onItemsRendered`、`ref` 传给 Grid,并设置 `columnCount`、`columnWidth`、`height`、`rowCount`、`rowHeight`、`width`、`renderCell`。
- 用 `InfiniteLoader` 包裹 `FixedSizeList`:传入 `isItemLoaded`、`loadMoreItems`、`itemCount`;child 接收 `onItemsRendered` 和 `ref`,把 `itemCount`、`itemSize`、`onItemsRendered`、`height`、`width`、`ref` 传给 `FixedSizeList`,并使用 `renderCell`。
- 从 `react-virtualized` 迁移到 `react-window` 和 `react-window-infinite-loader` 可能很快,但先检查缺失组件,例如 `WindowScroller` 和 `AutoSizer`。
- 复杂需求可组合:用 `AutoSizer disableHeight` 获取 `width`,计算 `rowCount`,再使用 `InfiniteLoader`、`WindowScroller`。
- 如果需要 `WindowScroller` 的功能(让 List 根据 window 的滚动位置滚动),`react-window` 没有实现,需要在 userland 自行解决。
- 如果需要 `AutoSizer` 的功能(自动撑满可用空间并调整单个 child 的宽高),使用独立包 `react-virtualized-auto-sizer` 或关注对应 issue。
- 如果需要 `CellMeasurer` 的功能(通过不可见渲染自动测量 cell 内容),关注对应 issue 或另寻方案。
- 现代浏览器中,对长 HTML 文档渲染开销大时,尝试 CSS `content-visibility: auto`,它允许跳过离屏内容的渲染和绘制。
- 渲染动态内容列表时,仍推荐用 `react-window` 这类库;不要指望 `content-visibility: hidden` 版本能胜过积极使用 `display:none` 或移除离屏 DOM 节点的虚拟化库。
适用边界 · 注意事项
- 列表很短(约 100 项以下),原生渲染已经足够快时,不要虚拟化。
- 无障碍要求所有列表项必须存在于 DOM 中供屏幕阅读器读取时,不要虚拟化。
- 列表项高度不可预测且依赖内容,导致虚拟化测量不可靠时,不要虚拟化。