virtual-lists
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseList Virtualization
列表虚拟化
List virtualization (also known as windowing) is the idea of rendering only visible rows of content in a dynamic list instead of the entire list. The rows rendered are only a small subset of the full list with what is visible (the window) moving as the user scrolls. This can improve rendering performance.
If you use React and need to display large lists of data efficiently, you may be familiar with react-virtualized. It's a windowing library by Brian Vaughn that renders only the items currently visible in a list (within a scrolling "viewport"). This means you don't need to pay the cost of thousands of rows of data being rendered at once.
列表虚拟化(也称为窗口化)是指在动态列表中仅渲染可见行内容,而非整个列表的技术。渲染的行只是完整列表中的一小部分,随着用户滚动,可见区域(窗口)会随之移动。这能够提升渲染性能。
如果你使用React,并且需要高效显示大型数据列表,你可能对react-virtualized并不陌生。它是由Brian Vaughn开发的窗口化库,仅渲染列表中当前可见的项(在滚动“视口”内)。这意味着你无需一次性渲染数千行数据,从而避免额外性能开销。
When to Use
适用场景
- Use this when rendering large lists or grids (hundreds/thousands of items) that cause performance issues
- This is helpful for reducing initial render time and improving scroll performance
- 当渲染大型列表或网格(数百/数千个项)导致性能问题时使用
- 有助于减少初始渲染时间并提升滚动性能
Instructions
实现步骤
- Use (or
react-window) to render only visible items in a scrollable containerreact-virtualized - Choose for items of equal height or
FixedSizeListfor items of different heightsVariableSizeList - Use for incrementally loading data as the user scrolls
react-window-infinite-loader - Consider CSS for simpler cases where full virtualization isn't needed
content-visibility: auto
- 使用(或
react-window)在可滚动容器中仅渲染可见项react-virtualized - 若列表项高度相同,选择;若高度不同,选择
FixedSizeListVariableSizeList - 使用实现用户滚动时增量加载数据
react-window-infinite-loader - 若无需完整虚拟化,可考虑使用CSS属性来处理简单场景
content-visibility: auto
Details
详细说明
How does list virtualization work?
列表虚拟化的工作原理
"Virtualizing" a list of items involves maintaining a window and moving that window around your list. Windowing in react-virtualized works by:
- Having a small container DOM element (e.g ) with relative positioning (window)
<ul> - Having a big DOM element for scrolling
- Absolutely positioning children inside the container, setting their styles for top, left, width and height.
Rather than rendering 1000s of elements from a list at once (which can cause slower initial rendering or impact scroll performance), virtualization focuses on rendering just items visible to the user.
This can help keep list rendering fast on mid to low-end devices. You can fetch/display more items as the user scrolls, unloading previous entries and replacing them with new ones.
对列表项进行“虚拟化”需要维护一个窗口并在列表中移动该窗口。react-virtualized中的窗口化通过以下方式实现:
- 创建一个小的相对定位容器DOM元素(例如)作为窗口
<ul> - 创建一个用于滚动的大DOM元素
- 将子元素绝对定位在容器内,设置其top、left、width和height样式
虚拟化技术不会一次性渲染列表中的数千个元素(这会导致初始渲染变慢或影响滚动性能),而是专注于仅渲染用户可见的项。
这有助于在中低端设备上保持列表渲染的流畅性。你可以在用户滚动时获取/显示更多项,同时卸载之前的条目并替换为新条目。
A smaller alternative to react-virtualized
react-virtualized的轻量替代方案
react-window is a rewrite of react-virtualized by the same author aiming to be smaller, faster and more tree-shakeable.
In a tree-shakeable library, size is a function of which API surfaces you choose to use. You can see ~20-30KB (gzipped) savings using it in place of react-virtualized.
The APIs for both packages are similar and where they differ, react-window tends to be simpler. react-window's components include:
react-window是同一作者对react-virtualized的重写版本,旨在更轻量、更快且更支持tree-shakeable(摇树优化)。
在支持摇树优化的库中,体积大小取决于你使用的API范围。与react-virtualized相比,使用react-window可节省约20-30KB(gzip压缩后)的体积。
两个库的API相似,不同之处在于react-window的API更简洁。react-window的组件包括:
List
List
Lists render a windowed list (row) of elements meaning that only the visible rows are displayed to users (e.g FixedSizeList, VariableSizeList). Lists use a Grid (internally) to render rows, relaying props to that inner Grid.
Rendering a list of data using React
Here's an example of rendering a list of simple data () using React:
itemsArrayjs
import React from "react";
import ReactDOM from "react-dom";
const itemsArray = [
{ name: "Drake" },
{ name: "Halsey" },
{ name: "Camillo Cabello" },
{ name: "Travis Scott" },
{ name: "Bazzi" },
{ name: "Flume" },
{ name: "Nicki Minaj" },
{ name: "Kodak Black" },
{ name: "Tyga" },
{ name: "Buno Mars" },
{ name: "Lil Wayne" }, ...
]; // our data
const Row = ({ index, style }) => (
<div className={index % 2 ? "ListItemOdd" : "ListItemEven"} style={style}>
{itemsArray[index].name}
</div>
);
const Example = () => (
<div
style={{
height: 150,
width: 300
}}
class="List"
>
{itemsArray.map((item, index) => Row({ index }))}
</div>
);
ReactDOM.render(<Example />, document.getElementById("root"));Rendering a list using react-window
...and here's the same example using react-window's , which takes a few props (, , , ) and a row rendering function passed as a child:
FixedSizeListwidthheightitemCountitemSizejs
import React from "react";
import ReactDOM from "react-dom";
import { FixedSizeList as List } from "react-window";
const itemsArray = [...]; // our data
const Row = ({ index, style }) => (
<div className={index % 2 ? "ListItemOdd" : "ListItemEven"} style={style}>
{itemsArray[index].name}
</div>
);
const Example = () => (
<List
className="List"
height={150}
itemCount={itemsArray.length}
itemSize={35}
width={300}
>
{Row}
</List>
);
ReactDOM.render(<Example />, document.getElementById("root"));You can try out on CodeSandbox.
FixedSizeListList组件用于渲染窗口化的列表行元素,即仅向用户显示可见行(例如FixedSizeList、VariableSizeList)。List内部使用Grid组件渲染行,并将属性传递给内部的Grid。
使用React渲染数据列表
以下是使用React渲染简单数据列表()的示例:
itemsArrayjs
import React from "react";
import ReactDOM from "react-dom";
const itemsArray = [
{ name: "Drake" },
{ name: "Halsey" },
{ name: "Camillo Cabello" },
{ name: "Travis Scott" },
{ name: "Bazzi" },
{ name: "Flume" },
{ name: "Nicki Minaj" },
{ name: "Kodak Black" },
{ name: "Tyga" },
{ name: "Buno Mars" },
{ name: "Lil Wayne" }, ...
]; // 数据源
const Row = ({ index, style }) => (
<div className={index % 2 ? "ListItemOdd" : "ListItemEven"} style={style}>
{itemsArray[index].name}
</div>
);
const Example = () => (
<div
style={{
height: 150,
width: 300
}}
class="List"
>
{itemsArray.map((item, index) => Row({ index }))}
</div>
);
ReactDOM.render(<Example />, document.getElementById("root"));使用react-window渲染列表
以下是使用react-window的实现相同功能的示例,该组件接受几个属性(、、、),并将行渲染函数作为子组件传入:
FixedSizeListwidthheightitemCountitemSizejs
import React from "react";
import ReactDOM from "react-dom";
import { FixedSizeList as List } from "react-window";
const itemsArray = [...]; // 数据源
const Row = ({ index, style }) => (
<div className={index % 2 ? "ListItemOdd" : "ListItemEven"} style={style}>
{itemsArray[index].name}
</div>
);
const Example = () => (
<List
className="List"
height={150}
itemCount={itemsArray.length}
itemSize={35}
width={300}
>
{Row}
</List>
);
ReactDOM.render(<Example />, document.getElementById("root"));你可以在CodeSandbox上尝试。
FixedSizeListGrid
Grid
Grid renders tabular data with virtualization along the vertical and horizontal axes (e.g FixedSizeGrid, VariableSizeGrid). It only renders the Grid cells needed to fill itself based on current horizontal/vertical scroll positions.
js
import React from 'react';
import ReactDOM from 'react-dom';
import { FixedSizeGrid as Grid } from 'react-window';
const itemsArray = [
[{},{},{},...],
[{},{},{},...],
[{},{},{},...],
[{},{},{},...],
];
const Cell = ({ columnIndex, rowIndex, style }) => (
<div
className={
columnIndex % 2
? rowIndex % 2 === 0
? 'GridItemOdd'
: 'GridItemEven'
: rowIndex % 2
? 'GridItemOdd'
: 'GridItemEven'
}
style={style}
>
{itemsArray[rowIndex][columnIndex].name}
</div>
);
const Example = () => (
<Grid
className="Grid"
columnCount={5}
columnWidth={100}
height={150}
rowCount={5}
rowHeight={35}
width={300}
>
{Cell}
</Grid>
);
ReactDOM.render(<Example />, document.getElementById('root'));You can also try out on CodeSandbox.
FixedSizeGridGrid组件用于渲染表格数据,并在垂直和水平方向上实现虚拟化(例如FixedSizeGrid、VariableSizeGrid)。它仅根据当前水平/垂直滚动位置渲染填充自身所需的网格单元格。
js
import React from 'react';
import ReactDOM from 'react-dom';
import { FixedSizeGrid as Grid } from 'react-window';
const itemsArray = [
[{},{},{},...],
[{},{},{},...],
[{},{},{},...],
[{},{},{},...],
];
const Cell = ({ columnIndex, rowIndex, style }) => (
<div
className={
columnIndex % 2
? rowIndex % 2 === 0
? 'GridItemOdd'
: 'GridItemEven'
: rowIndex % 2
? 'GridItemOdd'
: 'GridItemEven'
}
style={style}
>
{itemsArray[rowIndex][columnIndex].name}
</div>
);
const Example = () => (
<Grid
className="Grid"
columnCount={5}
columnWidth={100}
height={150}
rowCount={5}
rowHeight={35}
width={300}
>
{Cell}
</Grid>
);
ReactDOM.render(<Example />, document.getElementById('root'));你也可以在CodeSandbox上尝试。
FixedSizeGridMore in-depth react-window examples
更多react-window深度示例
Scott Taylor implemented an open-source Pitchfork music reviews scraper (src) using and .
react-windowFixedSizeGridPitchfork scraper uses react-window-infinite-loader (demo) which helps break large data sets down into chunks that can be loaded as they are scrolled into view.
Here's a snippet of how react-window-infinite-loader is incorporated in this app:
js
import React, { Component } from 'react';
import { FixedSizeGrid as Grid } from 'react-window';
import InfiniteLoader from 'react-window-infinite-loader';
// ...
render() {
return (
<InfiniteLoader
isItemLoaded={this.isItemLoaded}
loadMoreItems={this.loadMoreItems}
itemCount={this.state.count + 1}
>
{({ onItemsRendered, ref }) => (
<Grid
onItemsRendered={this.onItemsRendered(onItemsRendered)}
columnCount={COLUMN_SIZE}
columnWidth={180}
height={800}
rowCount={Math.max(this.state.count / COLUMN_SIZE)}
rowHeight={220}
width={1024}
ref={ref}
>
{this.renderCell}
</Grid>
)}
</InfiniteLoader>
);
}You might find the commit porting the app over from useful.
react-virtualizedAn implementation using is also available:
FixedSizeListjs
return (
<InfiniteLoader
isItemLoaded={this.isItemLoaded}
loadMoreItems={this.loadMoreItems}
itemCount={this.state.count}
>
{({ onItemsRendered, ref }) => (
<section>
<FixedSizeList
itemCount={this.state.count}
itemSize={ROW_HEIGHT}
onItemsRendered={onItemsRendered}
height={this.state.height}
width={this.state.width}
ref={ref}
>
{this.renderCell}
</FixedSizeList>
</section>
)}
</InfiniteLoader>
);For even more complex needs, a The Movie Database demo app used react-virtualized and Infinite Loader under the hood. Porting it over to react-window and react-window-infinite-loader didn't take long, but we did discover a few components were not yet supported. Regardless, the final functionality is pretty close.
The missing components were WindowScroller and AutoSizer, which we'll look at next.
js
// ...
return (
<section>
<AutoSizer disableHeight>
{({width}) => {
const {movies, hasMore} = this.props;
const rowCount = getRowsAmount(width, movies.length, hasMore);
// ...
return (
<InfiniteLoader
ref={this.infiniteLoaderRef}
// ...
{({onRowsRendered, registerChild}) => (
<WindowScroller>
{({height, scrollTop}) => (该爬虫使用了react-window-infinite-loader(演示),它有助于将大型数据集拆分为多个块,当用户滚动到对应区域时再加载这些块。
以下是该应用中集成react-window-infinite-loader的代码片段:
js
import React, { Component } from 'react';
import { FixedSizeGrid as Grid } from 'react-window';
import InfiniteLoader from 'react-window-infinite-loader';
// ...
render() {
return (
<InfiniteLoader
isItemLoaded={this.isItemLoaded}
loadMoreItems={this.loadMoreItems}
itemCount={this.state.count + 1}
>
{({ onItemsRendered, ref }) => (
<Grid
onItemsRendered={this.onItemsRendered(onItemsRendered)}
columnCount={COLUMN_SIZE}
columnWidth={180}
height={800}
rowCount={Math.max(this.state.count / COLUMN_SIZE)}
rowHeight={220}
width={1024}
ref={ref}
>
{this.renderCell}
</Grid>
)}
</InfiniteLoader>
);
}你可以查看将应用从迁移过来的提交记录,这会对你有所帮助。
react-virtualized还有一个使用的实现示例:
FixedSizeListjs
return (
<InfiniteLoader
isItemLoaded={this.isItemLoaded}
loadMoreItems={this.loadMoreItems}
itemCount={this.state.count}
>
{({ onItemsRendered, ref }) => (
<section>
<FixedSizeList
itemCount={this.state.count}
itemSize={ROW_HEIGHT}
onItemsRendered={onItemsRendered}
height={this.state.height}
width={this.state.width}
ref={ref}
>
{this.renderCell}
</FixedSizeList>
</section>
)}
</InfiniteLoader>
);对于更复杂的需求,有一个基于The Movie Database的演示应用,它底层使用了react-virtualized和Infinite Loader。将其迁移到react-window和react-window-infinite-loader并不需要太长时间,但我们发现有一些组件尚未被支持。不过最终实现的功能与原应用非常接近。
缺失的组件是WindowScroller和AutoSizer,我们接下来会介绍它们。
js
// ...
return (
<section>
<AutoSizer disableHeight>
{({width}) => {
const {movies, hasMore} = this.props;
const rowCount = getRowsAmount(width, movies.length, hasMore);
// ...
return (
<InfiniteLoader
ref={this.infiniteLoaderRef}
// ...
{({onRowsRendered, registerChild}) => (
<WindowScroller>
{({height, scrollTop}) => (What's missing from react-window?
react-window缺失的功能
react-window does not yet have the complete API surface of react-virtualized, so do check the comparison docs if considering it. What's missing?
- WindowScroller - This is a component that enables Lists to be scrolled based on the window's scroll positions. There are currently no plans to implement this for react-window so you'll need to solve this in userland.
react-virtualized - AutoSizer - HOC that grows to fit all of the available space, automatically adjusting the width and height of a single child. Brian implemented this as a standalone package. Follow this issue for the latest.
- CellMeasurer - HOC automatically measuring a cell's content by rendering it in a way that is not visible to the user. Follow here for discussion on support.
That said, we found react-window sufficient for most of our needs with what it includes out of the box.
react-window目前的API覆盖范围不如react-virtualized完整,因此在考虑使用它时,请查看对比文档。缺失的功能包括:
- WindowScroller - 这是react-virtualized的一个组件,允许列表根据窗口的滚动位置进行滚动。目前暂无为react-window实现该组件的计划,你需要自行实现相关逻辑。
- AutoSizer - 一个高阶组件,可自适应可用空间,自动调整单个子组件的宽度和高度。Brian将其作为一个独立包发布。你可以关注这个议题获取最新动态。
- CellMeasurer - 一个高阶组件,通过在不可见区域渲染单元格内容来自动测量其尺寸。你可以关注这里了解相关支持情况。
尽管如此,我们发现react-window自带的功能已经能满足大多数需求。
Improvements in the web platform
Web平台的改进
Some modern browsers now support CSS content-visibility. allows you to skip rendering & painting offscreen content until needed. If you have a long HTML document with costly rendering, consider trying the property out.
content-visibility:autoFor rendering lists of dynamic content, I still recommend using a library like react-window. It would be hard to have a version of such a library that beats a version aggressively using or removing DOM nodes when offscreen like many list virtualization libraries may do today.
content-visibility:hiddendisplay:none一些现代浏览器现在支持CSS content-visibility属性。允许你跳过渲染和绘制屏幕外的内容,直到需要时再进行。如果你的长HTML文档渲染成本较高,可以尝试使用该属性。
content-visibility:auto对于动态内容列表的渲染,我仍然推荐使用react-window这样的库。目前很难实现一个基于的库,其性能能够超过像许多列表虚拟化库那样使用或在元素离开屏幕时移除DOM节点的实现。
content-visibility:hiddendisplay:noneFurther reading
扩展阅读
For further reading about react-window and react-virtualized, check out:
如需了解更多关于react-window和react-virtualized的内容,请查看: