react-virtualized:如何使用单独的 rowRenderer 进行高度测量

react-virtualized: how to use a separate rowRenderer for height measurement

我正在尝试使用 react-virtualized List 组件显示带有复杂标记的大型文本文档。该文档被分解成比某个最大值短的不同长度的文本块。 List 组件将这些块中的每一个呈现为一行。

由于块长度不同,我无法使用固定的行高,因此我想使用 CellMeasurer。问题是在每个块上生成标记所需的解析非常昂贵——这是我想使用 react-virtualized 的部分原因。即使所有的块都在后台渲染,还是太慢了。

由于标记不影响高度,我想使用一个更简单的 rowRenderer 函数来呈现没有标记的文本 仅用于测量行 ,然后提供一个单独的和更完整的 rowRenderer 用于每个带有标记的块的实际渲染。有办法吗?

诚然,这 确实 很老套,不是最佳实践,但您可以这样做:

<List
    rowHeight={(index) => {
        const rowData = data[index];
        let div = document.getElementById('renderer');
        if (!div) {
            div = document.createElement('div');
            div.id = 'renderer';
        }
        ReactDOM.render(div, <Row>{rowData}</Row>);
        const height = div.offsetHeight;
        ReactDOM.unmountComponentAtNode(div);
        if (index === data.length - 1) {
            div.parentNode.removeChild(div);
        }
        return height;
    }}
/>

或者,实际上,您可以有两个列表,一个带有 visibility: hidden,您只需在其中渲染每一行而不带标记,获取高度,然后将其添加到数组中。一旦数组的长度等于你的数据长度,你就不再显示它,然后渲染另一个,用rowHeight={index => heights[index]}

经过反复试验,我发现一个很好的方法是让 rowRenderer 函数决定以哪种方式呈现行。您可以通过检查列表中 CellMeasurerCache 实例使用中的 _rowHeightCache 属性 来执行此操作。请注意,_rowHeightCache 的键采用以下形式:“index-0”,其中 index 是行的索引。

以下是设置行渲染器的方法:

  TextRowRenderer(
    {
      key, // Unique key within array of rows
      index, // Index of row within collection
      // isScrolling, // The List is currently being scrolled
      style, // Style object to be applied to row (to position it)
      parent, // reference to List
    },
  ) {
    const { textArray } = this.props;

    // get the cached row height for this row
    const rowCache = this.listCache._rowHeightCache[`${index}-0`];

    // if it has been cached, render it using the normal, more expensive
    // version of the component, without bothering to wrap it in a
    // CellMeasurer (it's already been measured!)

    // Note that listCache.defaultHeight has been set to 0, to make the
    // the comparison easy
    if (rowCache !== null && rowCache !== undefined && rowCache !== 0) {
      return (
        <TextChunk
          key={key}
          chunk={textArray[index]}
          index={index}
          style={style}
        />
      );
    }

    // If the row height has not been cached (meaning it has not been
    // measured, return the text chunk component, but this time:
    // a) it's wrapped in CellMeasurer, which is configured with the
    //    the cache, and
    // b) it receives the prop textOnly={true}, which it tells it to
    //    to skip rendering the markup
    return (
      <CellMeasurer
        cache={this.listCache}
        columnIndex={0}
        key={key}
        parent={parent}
        rowIndex={index}
      >
        {() => {
          return (
            <TextChunk
              key={key}
              chunk={textArray[index]}
              index={index}
              style={style}
              textOnly
            />
          );
        }}
      </CellMeasurer>
    );
  }

然后以普通方式传递给 List 组件:

          <List
            height={pageHeight}
            width={pageWidth}
            rowCount={textArray.length}
            rowHeight={this.listCache.rowHeight}
            rowRenderer={this.TextRowRenderer}
            deferredMeasurementCache={this.listCache}
            style={{ outline: 'none' }}
            ref={(r) => { this.listRef = r; }}
          />

通过包含一个参考回调,我们现在可以访问 measureAllRows 方法,我们可以用它来强制 List 提前渲染所有行以获得高度。这会 确保滚动条功能正常,但即使 textOnly 标志可能需要更长的时间。就我而言,我相信 值得等待。

以下是调用 measureAllRows 的方法:

  componentDidUpdate() {
    const {
      textArray,
    } = this.props;

    // We will only run measureAllRows if they have not been
    // measured yet. An easy way to check is to see if the
    // last row is measured
    const lastRowCache = this
       .listCache
       ._rowHeightCache[`${textArray.length - 1}-0`];

    if (this.listRef
      || lastRowCache === null
      || lastRowCache === undefined
      || lastRowCache === 0) {
      try {
        this.listRef.measureAllRows();
      } catch {
        console.log('failed to measure all rows');
      }
    }
  }

try-catch 块是必需的,因为如果它试图测量 在构建 List 之前,它会抛出一个 index-out-of -边界错误。

最后的想法

另一种可能的方法是让实际的 List 组件以缓存的测量为条件,而不是 rowRenderer 函数。您可以将文本数组呈现为 常规列表,将每一行包装在 <CellMeasurer> 中。当。。。的时候 缓存已满,然后您将渲染一个虚拟 List 配置 使用预填充缓存。这将避免调用 measureAllRowscomponentDidUpdateuseEffect 回调中。