react-virtualized 的文档化 API 不支持在 Table 中使用 CellMeasurer。这在 react-virtualized 中留下了一些选项,包括:
- 使用
Grid 和外部列标题实现
- 使用
Table 实现对 API 中未记录的内部组件的依赖
以下概述了适用于 react-virtualized v9.21.1(截至 2019 年 7 月的最新版本)的后一种方法的解决方案。当然,这种方法有可能在将来的 react-virtualized 版本中更改内部结构会破坏某些东西。
有几个问题需要处理,包括:
-
Table 在内部使用 Grid 来提供虚拟化滚动,但不会在 API 中任何需要的地方公开它。
-
Grid 只有一列,其中包含一行中的所有Column 单元格,但Grid 作为渲染Column 单元格的父级传递。因此,一个Grid 单元格可以与多个Column 单元格相关联,Grid 和CellMeasurer 不支持这种情况。
- 在
Grid 中使用CellMeasurer 依赖于Grid 直接管理一行中的所有单元格,而没有干预rowRenderer,而Table 有自己的行渲染逻辑。
[在下面的代码示例中,一些数据元素和函数使用模块级声明显示。实际上,它们可以被定义为包含 Table 的组件的成员,或者在某些情况下,可能作为道具传递给包含 Table 的组件。]
以下解决方案在一般情况下解决了这些问题:
- 静态表数据
- 静态行、列和单元格格式
- 一列中单元格的高度在各行之间是可变的
- 多列可以有这样的可变高度单元格
在这种情况下,使用了两个 CellMeasurerCache 实例。 cellCache 用于单个 Column 单元格的高度。 rowCache 表示行的高度。
const cellCache = new CellMeasurerCache({
fixedWidth: true,
defaultHeight: 20, // keep this <= any actual row height
minHeight: 10, // keep this <= any actual row height
});
const rowCache = new CellMeasurerCache({
fixedWidth: true,
defaultHeight: 37, // tune as estimate for unmeasured rows
minHeight: 10, // keep this <= any actual row height
});
对于Table 组件:
- 将
rowCache 添加为deferredMeasurementCache 属性
- 添加
rowRenderer函数
- 添加
rowHeight函数
<Table
...
deferredMeasurementCache={rowCache}
rowRenderer={rowRenderer}
rowHeight={rowHeight}
>
这些功能将在后面展示。 Table 不会对 deferredMeasurementCache 做任何事情,除非将其作为道具传递给 Table 的 Grid。
对于每个需要测量的Column,添加一个cellRenderer 函数。在许多更简单的情况下,可以对所有测量列使用相同的函数:
<Column
...
cellRenderer={measuredCellRenderer}
/>
为了帮助协调两个缓存的使用,需要三个额外的数据项:
const aMeasuredColumnIndex = 2; // any measured column index will do
let rowParent = null; // let a cellRenderer supply a usable value
const cellParent = { // an intermediary between measured row cells
// and their true containing Grid
invalidateCellSizeAfterRender: ({rowIndex}) => {
if (rowParent &&
typeof rowParent.invalidateCellSizeAfterRender == 'function') {
rowParent.invalidateCellSizeAfterRender({columnIndex: 0, rowIndex});
}
},
}
rowParent 用于将 Table 的 Grid 暴露给 rowRenderer。 cellParent 充当两个缓存之间以及行、其Column 单元和Table 的Grid 之间的中介。
接下来是前面提到的三个函数:
function measuredCellRenderer({rowIndex, columnIndex, parent, cellData}) {
rowParent = parent; // parent is the Table's grid,
// save it for use by rowRenderer
return (
<CellMeasurer
cache={cellCache}
columnIndex={columnIndex}
parent={cellParent}
rowIndex={rowIndex}
>
<div>{cellData}</div>
</CellMeasurer>
);
// Note: cellData is wrapped in a <div> to facilitate height
// styling, for example adding padding to the <div>, because CellMeasurer
// measures the height of the content box.
}
function rowRenderer(params) {
return (
<CellMeasurer
cache={rowCache}
columnIndex={0}
key={params.key}
parent={rowParent}
rowIndex={params.rowIndex}
>
{Table.defaultProps.rowRenderer(params)}
</CellMeasurer>
);
}
function rowHeight({index}) {
let cellCacheRowHeight = cellCache.rowHeight({index});
if (cellCache.has(index, aMeasuredColumnIndex)) {
rowCache.set(index, 0, 20, cellCacheRowHeight);
// the 20 above is a somewhat arbitrary number for width,
// which is not relevant
}
return cellCacheRowHeight;
}
请注意,CellMeasurer 有两种不同的用法。一个在measuredCellRenderer 函数内部,使用cellCache 和cellParent。另一个在rowRenderer函数内部,使用rowCache和rowParent。
另外,rowHeight 函数不只是报告行的高度。它还负责将cellCache 中的行rowHeight 转移到rowCache 中第一列也是唯一一列的行单元格高度。
当表格只有一个测量列时,可以稍微简化此解决方案。只需要一个CellMeasurerCache。单个缓存可以满足
cellCache 和 rowCache 的角色。结果:
- 不需要
cellParent;它可以被删除。对cellParent 的引用可以替换为对rowParent 的引用,或者在measuredCellRenderer 函数的情况下,CellMeasurer parent 属性可以直接设置为parent 函数参数。
- 在
measuredCellRenderer 内部,CellMeasurer 需要硬编码为columnIndex={0},即使测量的列不是表中的第一列。
-
rowHeight 函数中的 if 语句可以删除,因为不需要在两个缓存之间传输高度。
- 可以删除
aMeasuredColumnIndex,因为它只在rowHeight if 语句中被引用。