react-window 是一个 React 组件库,用于高效渲染大型列表和表格数据。其原理是仅渲染可见视口部分的数据来提升性能,从而减少初始渲染时间和内存占用,这也是虚拟化技术的一种。
另外,react-window 提供地 API 非常简单易用,你可以将它轻松集成到现有 React 项目中。不仅如此,react-window 以与 react-virtualized-auto-sizer 和 react-window-infinite-loader 等库一起使用,实现诸如自动调整大小和无限滚动的更高级的功能。
本篇我们只介绍 react-window 的基础常规使用方法。
安装
bash
# Yarn
yarn add react-window
# NPM
npm install --save react-window
列表类型
我们已经说过,react-window 的强项在于渲染大型列表数据。列表依据不同的维度可以简单划分成 2 大类型。
首先,依据排列方向,我们可以将列表分为:垂直列表(vertical list)和水平列表(horizontal list)。
- 垂直列表:每个列表项占据一行

- 水平列表:整个列表在一行排列,每个列表项占据其中一列

当然,在我实际接触到的项目里,垂直列表更加普遍,水平列表就比较少用了。
其次,如果是依据列表项大小,我们可以将列表分为:固定尺寸列表(fixed size list)和可变尺寸列表(variable size list)。
当然,一个列表项同时存在宽和高 2 个维度,我们所说的固定和可变,具体要依据列表方向,所指带的是其中一个维度。具体来说:
- 对垂直列表 而言,所说的固定/可变尺寸是指列表项的高。
此时,固定尺寸列表的每个项目的高度是一样的;而可变尺寸列表的每个项目的高度可能不一样。


- 同理,对水平列表 而言,所说的固定/可变尺寸是指列表项的宽。
此时,固定尺寸列表的每个项目的宽度是一样的;而可变尺寸列表的每个项目的宽度可能不一样。


在 react-window 中,固定尺寸列表和可变尺寸列表分别使用 <FixedSizeList>
和 <VariableSizeList>
组件表示。
jsx
// 引入固定尺寸列表
import { FixedSizeList } from 'react-window';
// 引入可变尺寸列表
import { VariableSizeList } from 'react-window';
接下来,我们就来学习这 2 个组件的使用。
使用列表组件
FixedSizeList - 默认
demo 地址。编写如下代码:
jsx
import { FixedSizeList } from 'react-window';
const Row = ({ index, style }) => (
<div style={style}>Row {index}</div>
);
const Example = () => (
<FixedSizeList
height={150}
itemCount={1000}
itemSize={35}
width={300}
>
{Row}
</FixedSizeList>
);
FixedSizeList 的 API 很简单:
-
height 和 width 呢,就是用来指定列表容器的高和宽的,我们的列表内容肯定会超过容器的,因此会有一个 overflow: auto 设置。当然,默认 FixedSizeList 渲染的是垂直列表,你可以通过 layout prop 控制变成水平的(这块不急,接下来会介绍)
-
itemCount 和 itemSize 也是实现虚拟的必需参数,itemSize 表示列表项目尺寸是 35px,垂直列表的话这表示的是列表项目的高为 35px。列表项目的高度有了,然后再加上列表项的个数,我们就能得到这个列表的总高度,就能实现我们的虚拟滚动了。
最后,我们来查看效果。

值得注意的是,<FixedSizeList>
组件因为是通过绝对定位控制虚拟列表滚动的,因此你必须要给行组件 **<Row>**
透传来自 **<FixedSizeList>**
的 style 样式对象,否则虚拟滚动将不会正常工作。

如果你没有透传 style 给 <Row>
,会看到类似下面这样的问题。

FixedSizeList - 水平列表
以上我们谈论的是<FixedSizeList>
默认布局即垂直列表下的表现行为。而如果想让 fix size list 的水平排列,只需要将 layout prop 设置成 horizontal 都可以。
demo 地址。代码如下:
jsx
import { FixedSizeList } from 'react-window';
const Column = ({ index, style }) => (
<div style={style}>Column {index}</div>
);
const Example = () => (
<FixedSizeList
height={75}
itemCount={1000}
itemSize={100}
layout="horizontal"
width={300}
>
{Column}
</FixedSizeList>
);
这个 demo 就比默认列表多了一个 layout="horizontal" 的设置,其次由于 FixedSizeList 的 children 不再是行的概念而是列,因为我们将 Row 更名更符合语义的 Column。
- height 和 width 呢,就是用来控制水平列表容器的高和宽
- itemCount 没什么好说的,itemSize 就不再表示列表项目的高了 ,而是宽,即水平列表下每个项目的宽度是 100px
查看效果:

当然,水平列表项也是通过绝对定位布局实现项目移动的。不过与垂直列表通过 top 实现上下偏移移动不同的是,水平列表项是通过 left 实现左右偏移移动的。

VariableSizeList - 默认
讲完固定尺寸列表,再来看看可变尺寸列表。其使用与 FixedSizeList 类似,唯一不同的地方就是 itemSize 类型 从 number 变成 (index) => number。demo 地址。
jsx
import { VariableSizeList } from 'react-window';
// These row heights are arbitrary.
// Yours should be based on the content of the row.
const rowHeights = new Array(1000)
.fill(true)
.map(() => 25 + Math.round(Math.random() * 50));
const getItemSize = index => rowHeights[index];
const Row = ({ index, style }) => (
<div style={style}>Row {index}</div>
);
const Example = () => (
<VariableSizeList
height={150}
itemCount={1000}
itemSize={getItemSize}
width={300}
>
{Row}
</VariableSizeList>
);
getItemSize 用于获取对应数组索引下那个列表项目的尺寸,在本场景(垂直列表)中表示高。
来看看效果。

VariableSizeList - 水平列表
水平列表的设置一样。demo 地址。
jsx
import { VariableSizeList } from 'react-window';
// These column widths are arbitrary.
// Yours should be based on the content of the column.
const columnWidths = new Array(1000)
.fill(true)
.map(() => 75 + Math.round(Math.random() * 50));
const getItemSize = index => columnWidths[index];
const Column = ({ index, style }) => (
<div style={style}>Column {index}</div>
);
const Example = () => (
<VariableSizeList
height={75}
itemCount={1000}
itemSize={getItemSize}
layout="horizontal"
width={300}
>
{Column}
</VariableSizeList>
);
除了增加 layout="horizontal" 之外,Row 更名为水平列表下的列 Column。getItemSize 获取的就是每个列表项目的宽了。
来看看效果。

滚动时监听
有一个场景,如果你的列表项渲染成本太高。react-window 能够提供一个滚动时标识 isScrolling,你可以根据这个标识来动态渲染项目列表的 UI------比如用户滚动的比较快,那就没必要每个组件都需要实实在在计算渲染------就可以选选择在停止滚动时再计算渲染。
来看下例子。demo 地址。
jsx
import { FixedSizeList as List } from 'react-window';
const Row = ({ index, isScrolling, style }) => (
<div style={style}>
{isScrolling ? 'Scrolling' : `Row ${index}`}
</div>
);
const Example = props => (
<List useIsScrolling {...props}>
{Row}
</List>
);
为了使用滚动时标识,我们需要做 2 件事。
- 列表组建上通过设置 useIsScrolling 启用滚动时标识
- 由此,在渲染项目时,会多出一个 isScrolling 属性。当当前列表出在滚动时,isScrolling 为 true,不滚就为 false。
我们看下效果:

滚动到指定项
react-window 列表还提供了一个滚动到指定项的方法,是通过 ref 引用的方式实现。
jsx
import { FixedSizeList as List } from "react-window";
const Row = ({ index, style }) => (
<div className={index % 2 ? "ListItemOdd" : "ListItemEven"} style={style}>
Row {index}
</div>
);
const Example = () => {
listRef = React.createRef();
render() {
return (
<Fragment>
<div>
<button className="ExampleButton" onClick={this.scrollToRow200Auto}>
Scroll to row 200 (align: auto)
</button>
</div>
<List
className="List"
height={150}
itemCount={1000}
itemSize={35}
ref={this.listRef}
width={300}
>
{Row}
</List>
</Fragment>
);
}
scrollToRow200Auto = () => {
this.listRef.current.scrollToItem(200);
};
}
看看效果:

行为表现:
- 当项目已在视口时,调用没放反映
- 当项目处在视口上方时,调用会定位在顶部
- 当项目处在视口下方时,调用会定位在底部
当然,scrollToItem 方法也支持传入第 2 个参数,明确指定项目定位的位置。可选项有:auto(默认)、start、end 以及 center。最常用的可能就是 center 了。
jsx
this.listRef.current.scrollToItem(300, 'center');
再看看效果。

可以看到,不管何种场景,指定项目总是会强制居于视口中央。
总结
本文我们学习了高效渲染大型列表组件库 react-window 的使用,介绍了固定列表和可变列表的概念,并带大家学习 <FixedSizeList>
和 <VariableSizeList>
的使用;并学习 2 出可以提升使用体验的优化,滚动监听及滚动到指定项。
希望本文的内容对你的工作能有所帮助,感谢阅读。