这些库旨在解决 React 应用中渲染大量数据时的性能瓶颈。react-virtualized 和 react-window 采用窗口化(windowing)技术,仅渲染可视区域内的 DOM 节点,大幅提升性能。react-infinite 侧重于无限加载逻辑,但通常不具备虚拟化能力。react-list 和 react-tiny-virtual-list 提供了介于两者之间的解决方案,分别在灵活性和体积上有所侧重。开发者需根据项目对性能、维护性及功能复杂度的需求进行选择。
在构建数据密集型 React 应用时,列表渲染性能往往是首要瓶颈。当数据量达到成千上万条时,直接渲染所有 DOM 节点会导致页面卡顿甚至崩溃。本文对比的五款库 —— react-infinite、react-list、react-tiny-virtual-list、react-virtualized 和 react-window —— 提供了不同的解决思路。我们将深入探讨它们的核心机制、API 设计及维护状态,帮助你做出架构决策。
性能优化的核心在于减少 DOM 节点数量。
react-infinite 侧重于逻辑上的无限加载,而非 DOM 优化。
// react-infinite: 监听加载事件
<Infinite
elementHeight={200}
containerHeight={600}
onInfiniteLoad={this.handleLoadMore}
>
{items.map(item => <div key={item.id}>{item.name}</div>)}
</Infinite>
react-virtualized 和 react-window 采用真正的窗口化(Windowing)技术。
// react-virtualized: 使用 List 组件
<List
width={300}
height={600}
rowCount={items.length}
rowHeight={50}
rowRenderer={({ index, style }) => (
<div key={items[index].id} style={style}>{items[index].name}</div>
)}
/>
// react-window: 使用 FixedSizeList 组件
<FixedSizeList
height={600}
itemCount={items.length}
itemSize={50}
width={300}
>
{({ index, style }) => (
<div key={items[index].id} style={style}>{items[index].name}</div>
)}
</FixedSizeList>
react-list 和 react-tiny-virtual-list 也提供虚拟化。
react-list 允许更灵活的 item 渲染器配置。react-tiny-virtual-list 专注于极简实现。// react-list: 使用 itemRenderer
<List
itemRenderer={this.renderItem}
length={items.length}
estimateItemSize={50}
/>
// react-tiny-virtual-list: 简洁 API
<VirtualList
width={300}
height={600}
itemSize={50}
items={items}
renderItem={({ index, style }) => (
<div key={items[index].id} style={style}>{items[index].name}</div>
)}
/>
API 的设计风格直接影响开发体验和代码可维护性。
react-virtualized 诞生于 React 类组件时代。
ref 回调和类组件生命周期。// react-virtualized: 通常需要配合 ref 使用
class MyList extends React.Component {
render() {
return <List ref={this.listRef} ... />;
}
}
react-window 为现代 React 而生。
// react-window: 原生支持函数组件
function MyList({ items }) {
return (
<FixedSizeList itemCount={items.length} ...>
{({ index, style }) => <div style={style}>{items[index]}</div>}
</FixedSizeList>
);
}
react-infinite 同样基于较旧的 React 模式。
// react-infinite: 配置较为繁琐
<Infinite
useWindowScroll={true}
containerHeight={this.state.height}
...
/>
react-list 和 react-tiny-virtual-list 介于两者之间。
react-list 提供稳定的 API,但配置项较多。react-tiny-virtual-list 力求简单,但扩展性有限。// react-tiny-virtual-list: 配置直观
<VirtualList
items={items}
renderItem={...}
/>
这是选型中最关键的非技术因素。
react-virtualized 已不再积极维护。
react-window。react-infinite 基本处于停滞状态。
react-window 是当前标准。
react-virtualized 同一作者维护,活跃度高。react-list 和 react-tiny-virtual-list 状态稳定。
react-window。不同场景需要不同的功能支持。
可变高度列表:
react-virtualized 使用 CellMeasurer 配合 List,配置复杂但功能强。react-window 提供 VariableSizeList,API 更简洁,需手动通知尺寸变化。react-list 通过 estimateItemSize 和重新渲染处理。react-tiny-virtual-list 支持有限,通常需固定高度或简单估算。react-infinite 不支持真正的可变高度虚拟化。// react-window: 可变高度
<VariableSizeList
itemSize={index => heights[index]}
itemCount={items.length}
...
/>
// 需调用 listRef.resetAfterIndex(index) 通知更新
// react-virtualized: 可变高度
<CellMeasurerCache defaultHeight={100}>
<List
rowHeight={cache.rowHeight}
rowRenderer={({ index, style, parent }) => (
<CellMeasurer cache={cache} index={index} parent={parent}>
{({ measure, didMeasure }) => (
<div style={style} onLoad={measure}>...</div>
)}
</CellMeasurer>
)}
/>
</CellMeasurerCache>
网格布局(Grid):
react-virtualized 和 react-window 均提供 Grid 组件。react-list、react-tiny-virtual-list、react-infinite 主要专注于单维列表。// react-window: 网格
<FixedSizeGrid
columnCount={12}
rowCount={100}
columnWidth={100}
rowHeight={50}
...
/>
| 特性 | react-window | react-virtualized | react-list | react-tiny-virtual-list | react-infinite |
|---|---|---|---|---|---|
| 虚拟化核心 | ✅ 是 | ✅ 是 | ✅ 是 | ✅ 是 | ❌ 否 (通常) |
| 维护状态 | 🟢 活跃 | 🔴 不再积极维护 | 🟡 稳定 | 🟡 稳定 | 🔴 停滞 |
| API 风格 | 🪝 Hooks 友好 | 🏛️ 类组件为主 | 🛠️ 配置型 | 🧸 极简 | 🕰️ 旧式 |
| 可变高度 | ✅ 支持 | ✅ 支持 (复杂) | ✅ 支持 | ⚠️ 有限 | ❌ 不支持 |
| 网格支持 | ✅ 支持 | ✅ 支持 | ❌ 否 | ❌ 否 | ❌ 否 |
| 推荐场景 | 新项目首选 | 旧项目维护 | 特殊定制 | 轻量场景 | 不推荐 |
在选择列表库时,请遵循以下原则:
默认选择 react-window:它是 react-virtualized 的精神继承者,更轻、更快、更符合现代 React 开发习惯。除非你有非常特殊的理由,否则不要偏离这个默认选项。
避免 react-virtualized 新接入:尽管它功能丰富,但维护状态已改变。引入它意味着未来可能需要重构迁移至 react-window,这会带来不必要的技术债务。
警惕 react-infinite 的性能陷阱:很多开发者误以为“无限加载”等于“性能优化”。实际上,如果没有虚拟化技术,加载 10,000 条数据依然会卡死浏览器。确保你选择的是具备“窗口化”能力的库。
评估复杂性:如果你的列表需要复杂的网格布局或交叉轴滚动,react-window 和 react-virtualized 是唯二的支持者。对于简单列表,react-tiny-virtual-list 可以作为轻量替代,但需权衡未来扩展性。
最终结论:现代 React 架构中,react-window 是列表虚拟化的事实标准。它平衡了性能、体积和开发体验,是大多数工程场景下的最优解。
仅用于维护旧项目。该库已不再积极维护,官方明确建议新用户迁移至 react-window。虽然功能全面(支持网格、表格等),但包体积较大且 API 基于较旧的 React 模式,新项目应避免使用。
不推荐用于高性能场景。该库主要处理无限加载逻辑,往往渲染所有 DOM 节点,导致长列表性能急剧下降。仅适用于数据量极小且只需简单分页加载的遗留系统,现代应用应避开。
适用于需要高度自定义渲染逻辑的场景。如果你需要比 react-window 更细粒度的控制,且能接受稍复杂的配置,它是一个稳定的备选方案。但在大多数标准列表场景中,react-window 通常更易上手。
适用于对包体积极度敏感的小型项目。如果你只需要基础的虚拟列表功能,且希望依赖尽可能少,这是一个轻量级选择。但功能丰富度不如 react-window,复杂场景可能受限。
首选方案。如果你正在启动新项目,react-window 是最佳选择。它由 react-virtualized 的作者维护,专为现代 React 设计,支持 Hooks,体积更小且 API 更简洁。除非你有特殊的遗留需求,否则应优先考虑它。
React components for efficiently rendering large lists and tabular data. Check out the demo for some examples.
The following wonderful companies have sponsored react-virtualized:
Learn more about becoming a sponsor!
react-windowIf you're considering adding react-virtualized to a project, take a look at react-window as a possible lighter-weight alternative. Learn more about how the two libraries compare here.
Install react-virtualized using npm.
npm install react-virtualized --save
ES6, CommonJS, and UMD builds are available with each distribution. For example:
// Most of react-virtualized's styles are functional (eg position, size).
// Functional styles are applied directly to DOM elements.
// The Table component ships with a few presentational styles as well.
// They are optional, but if you want them you will need to also import the CSS file.
// This only needs to be done once; probably during your application's bootstrapping process.
import 'react-virtualized/styles.css';
// You can import any component you want as a named export from 'react-virtualized', eg
import {Column, Table} from 'react-virtualized';
// But if you only use a few react-virtualized components,
// And you're concerned about increasing your application's bundle size,
// You can directly import only the components you need, like so:
import AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer';
import List from 'react-virtualized/dist/commonjs/List';
Note webpack 4 makes this optimization itself, see the documentation.
If the above syntax looks too cumbersome, or you import react-virtualized components from a lot of places, you can also configure a Webpack alias. For example:
// Partial webpack.config.js
{
alias: {
'react-virtualized/List': 'react-virtualized/dist/es/List',
},
...rest
}
Then you can just import like so:
import List from 'react-virtualized/List';
// Now you can use <List {...props} />
You can also use a global-friendly UMD build:
<link rel="stylesheet" href="path-to-react-virtualized/styles.css" />
<script src="path-to-react-virtualized/dist/umd/react-virtualized.js"></script>
Now you're ready to start using the components. You can learn more about which components react-virtualized has to offer below.
React Virtualized has very few dependencies and most are managed by NPM automatically.
However the following peer dependencies must be specified by your project in order to avoid version conflicts:
react,
react-dom.
NPM will not automatically install these for you but it will show you a warning message with instructions on how to install them.
By default all react-virtualized components use shallowCompare to avoid re-rendering unless props or state has changed.
This occasionally confuses users when a collection's data changes (eg ['a','b','c'] => ['d','e','f']) but props do not (eg array.length).
The solution to this is to let react-virtualized know that something external has changed. This can be done a couple of different ways.
The shallowCompare method will detect changes to any props, even if they aren't declared as propTypes.
This means you can also pass through additional properties that affect cell rendering to ensure changes are detected.
For example, if you're using List to render a list of items that may be re-sorted after initial render- react-virtualized would not normally detect the sort operation because none of the properties it deals with change.
However you can pass through the additional sort property to trigger a re-render.
For example:
<List {...listProps} sortBy={sortBy} />
Grid and Collection components can be forcefully re-rendered using forceUpdate.
For Table and List, you'll need to call forceUpdateGrid to ensure that the inner Grid is also updated. For MultiGrid, you'll need to call forceUpdateGrids to ensure that the inner Grids are updated.
API documentation available here.
There are also a couple of how-to guides:
Examples for each component can be seen in the documentation.
Here are some online demos of each component:
And here are some "recipe" type demos:
react-virtualized aims to support all evergreen browsers and recent mobile browsers for iOS and Android. IE 9+ is also supported (although IE 9 will require some user-defined, custom CSS since flexbox layout is not supported).
If you find a browser-specific problem, please report it along with a repro case. The easiest way to do this is probably by forking this Plunker.
Here are some great components built on top of react-virtualized:
Use GitHub issues for requests.
I actively welcome pull requests; learn how to contribute.
Changes are tracked in the changelog.
react-virtualized is available under the MIT License.