如何在反应虚拟表中使用CellMeasurer?

时间:2018-08-13 16:05:03

标签: reactjs react-virtualized

我使用react-virtualized Table渲染一个包含很多行的表。我不希望由于固定的列宽而修剪长文本,因此我想使用CellMeasurer动态测量宽度。

这是使用网格的示例。很好。

render() {
  const {width} = this.props;

  return (
  <Grid
    className={styles.BodyGrid}
    columnCount={1000}
    columnWidth={this._cache.columnWidth}
    deferredMeasurementCache={this._cache}
    height={400}
    overscanColumnCount={0}
    overscanRowCount={2}
    cellRenderer={this._cellRenderer}
    rowCount={50}
    rowHeight={35}
    width={width}
  />
  );
}

但是TableColumn都没有deferredMeasurementCache道具。我当前的代码如下:

return (
    <div>
      <AutoSizer disableHeight>
        {({width}) => (
          <Table
            ref="Table"
            disableHeader={disableHeader}
            headerClassName={styles.headerColumn}
            headerHeight={headerHeight}
            height={height}
            noRowsRenderer={this._noRowsRenderer}
            overscanRowCount={overscanRowCount}
            rowClassName={this._rowClassName}
            rowHeight={useDynamicRowHeight ? this._getRowHeight : rowHeight}
            rowGetter={rowGetter}
            rowCount={rowCount}
            scrollToIndex={scrollToIndex}
            sort={this._sort}
            sortBy={sortBy}
            sortDirection={sortDirection}
            width={width}>
              <Column
                label="Index"
                cellDataGetter={({rowData}) => rowData.index}
                dataKey="index"
                disableSort={!this._isSortEnabled()}
                width={60}
              />
              <Column .../>
          </Table>
        )}
      </Autosizer>
   </div>
);

如何在表格中使用Measurer

1 个答案:

答案 0 :(得分:2)

已记录的用于React-virtualized的API不支持在CellMeasurer中使用Table。剩下的一些选项可以在react-virtualized中使用,包括:

  • 使用Grid和外部列标题实现
  • 使用Table来实现,并且依赖于API中未记录的内部结构

以下概述了后一种方法的解决方案,该方法可与react-virtualized v9.21.1(截至2019年7月的最新版本)一起使用。当然,这种方法可能会在将来的版本中发生变化,从而改变某些东西。

有几个问题要处理,包括:

  • Table在内部使用Grid来提供虚拟化的滚动,但是并没有在需要的地方暴露在API中。
  • Grid只有一列,它包含一行中所有Column个单元格,但是Grid作为父级传递来呈现Column单元格。结果,一个Grid单元可以与许多Column单元相关联,GridCellMeasurer不支持这种情况。
  • CellMeasurer中使用Grid取决于Grid直接管理一行中的所有单元,而没有中间的rowRenderer,而Table有自己的行渲染逻辑。

[在下面的代码示例中,某些数据元素和函数与模块级声明一起显示。实际上,可以将它们定义为包含Table的组件的成员,或者在某些情况下,可以将它们作为props传递给包含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}
  >

稍后将显示功能。 TabledeferredMeasurementCache不会做任何事情,只是将它作为道具传递给Table的{​​{1}}。

对于每个需要测量的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}); } }, } 用于将表的网格暴露给rowRenderer。 rowParent充当两个缓存之间,一行,其cellParent单元和Column的{​​{1}}之间的中介。

接下来是前面提到的三个功能:

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; } 函数内部,并使用CellMeasurermeasuredCellRenderer。另一个在cellCache函数内部,并使用cellParentrowRenderer

此外,rowCache函数不仅报告行的高度。它还负责将rowParent中行的rowHeight转移到rowHeight中第一列也是唯一列的行单元格高度。

当表中只有一个测量列时,可以稍微简化此解决方案。只需要一个cellCache。单个缓存可以满足    rowCacheCellMeasurerCache的角色。结果:

  • 不需要cellCache;可以将其删除。对rowCache的引用可以由对cellParent的引用替换,或者在使用cellParent函数的情况下,可以将rowParent measuredCellRenderer道具直接设置为CellMeasurer函数参数。
  • parent内,parent必须为measuredCellRenderer进行硬编码,即使所测量的列不是表中的第一列。
  • 由于不需要在两个缓存之间传递高度,因此可以删除CellMeasurer函数中的columnIndex={0}语句。
  • if可以被删除,因为它仅在rowHeight aMeasuredColumnIndex语句中被引用。