【问题标题】:How to use CellMeasurer in react-virtualized Table?如何在反应虚拟化表中使用 CellMeasurer?
【发布时间】:2019-01-20 10:15:23
【问题描述】:

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

这是一个使用 Grid 的示例。它工作正常。

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

【问题讨论】:

标签: reactjs react-virtualized


【解决方案1】:

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 单元格相关联,GridCellMeasurer 不支持这种情况。
  • 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 做任何事情,除非将其作为道具传递给 TableGrid

对于每个需要测量的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 单元和TableGrid 之间的中介。

接下来是前面提到的三个函数:

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 函数内部,使用cellCachecellParent。另一个在rowRenderer函数内部,使用rowCacherowParent

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

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

  • 不需要cellParent;它可以被删除。对cellParent 的引用可以替换为对rowParent 的引用,或者在measuredCellRenderer 函数的情况下,CellMeasurer parent 属性可以直接设置为parent 函数参数。
  • measuredCellRenderer 内部,CellMeasurer 需要硬编码为columnIndex={0},即使测量的列不是表中的第一列。
  • rowHeight 函数中的 if 语句可以删除,因为不需要在两个缓存之间传输高度。
  • 可以删除aMeasuredColumnIndex,因为它只在rowHeight if 语句中被引用。

【讨论】:

    猜你喜欢
    • 2018-07-27
    • 2018-02-10
    • 2019-06-18
    • 1970-01-01
    • 1970-01-01
    • 2017-10-05
    • 2019-06-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多