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

Yix*_*Liu 7 reactjs react-virtualized

我使用 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}
  />
  );
}
Run Code Online (Sandbox Code Playgroud)

但无论是Table也Column有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>
);
Run Code Online (Sandbox Code Playgroud)

如何Measurer在表格中使用?

小智 7

react-virtualized 的文档化 API 不支持CellMeasurer在Table. 这在反应虚拟化中留下了一些选项,包括:

  • 实现使用Grid和外部列标题
  • 实现 usingTable依赖于 API 中未记录的内部组件

下面概述了后一种方法的解决方案,该方法适用于 react-virtualized v9.21.1(截至 2019 年 7 月的最新版本)。当然,这种方法有风险,即在未来版本的 react-virtualized 中更改内部结构会破坏某些东西。

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

  • Table在Grid内部使用 a来提供虚拟化滚动,但不会在需要的地方在 API 中公开它。
  • 在Grid只有一列,其中包含所有的Column一排电池,但Grid作为家长渲染传递Column细胞。其结果是,一个Grid小区可以与许多相关Column的细胞,这种情况Grid和 CellMeasurer不支持。
  • CellMeasurerin a 的使用Grid依赖于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
});
Run Code Online (Sandbox Code Playgroud)

对于Table组件:

  • 添加rowCache为deferredMeasurementCache道具
  • 添加rowRenderer功能
  • 添加rowHeight功能
  <Table
    ...
    deferredMeasurementCache={rowCache}
    rowRenderer={rowRenderer}
    rowHeight={rowHeight}
  >
Run Code Online (Sandbox Code Playgroud)

后面会介绍这些功能。 除了将它作为道具传递给's之外,Table不会deferredMeasurementCache对它做任何事情。TableGrid

对于Column需要测量的每一个,添加一个cellRenderer函数。在许多更简单的情况下,相同的函数可用于所有测量列:

  <Column
    ...
    cellRenderer={measuredCellRenderer}
  />
Run Code Online (Sandbox Code Playgroud)

为了帮助协调两个缓存的使用,需要三个额外的数据项:

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});
    }
  },
}
Run Code Online (Sandbox Code Playgroud)

rowParent用于将表格的网格暴露给 rowRenderer。cellParent充当两个缓存之间以及行、其Column单元格和Table's之间的中介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;
}
Run Code Online (Sandbox Code Playgroud)

请注意, 有两种不同的用法CellMeasurer。一种是在measuredCellRenderer函数内部并使用cellCacheand cellParent。另一个在rowRenderer函数内部并使用rowCacheand rowParent。

此外,该rowHeight函数不仅报告行的高度。它还负责将 .inrowHeight中cellCache第一列也是唯一一列的行的输入转换为行的单元格高度rowCache。

当表只有一个测量列时,可以稍微简化此解决方案。只CellMeasurerCache需要一个。单个高速缓存可以实现两者的作用 cellCache和rowCache。其结果:

  • 没有必要cellParent;它可以被移除。对的引用cellParent可以替换为对 的引用rowParent,或者在measuredCellRenderer函数的情况下,CellMeasurer parent可以将 prop 直接设置为parent函数参数。
  • 在 内部measuredCellRenderer,CellMeasurer需要对 进行硬编码columnIndex={0},即使测量列不是表中的第一列。
  • 该if内部声明rowHeight函数可以被删除,因为没有需要转移之间的二级缓存的高度。
  • 在aMeasuredColumnIndex可以删除,因为它是在唯一的参考rowHeight if声明。