Как использовать CellMeasurer в React-Virtualized Table?

Я использую 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}
  />
  );
}

Но ни 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>
);

Как я могу использовать Measurer в таблице?


person Yixing Liu    schedule 13.08.2018    source источник
comment
Должен ли я рассматривать один столбец как ячейку и использовать <CellMeasurer> для обтекания <Column>?   -  person Yixing Liu    schedule 13.08.2018
comment
Документация по API теперь предоставляет пример этого: bvaughn.github.io/react-virtualized / # / components / CellMeasurer с исходным кодом здесь: github.com/bvaughn/react-virtualized/blob/master/source/   -  person jeubank12    schedule 02.07.2020


Ответы (1)


Документированный API для react-virtualized не поддерживает использование CellMeasurer в Table. Таким образом, остается несколько вариантов в рамках react-virtualized, в том числе:

  • реализовать, используя Grid и внешние заголовки столбцов
  • реализовать с использованием Table с зависимостями от внутренних компонентов, не задокументированных в API

Ниже описывается решение последнего подхода, которое работает с реактивной виртуализацией v9.21.1 (последняя версия по состоянию на июль 2019 г.). Конечно, такой подход рискует, что изменения во внутреннем устройстве в будущих версиях react-virtualized что-то сломают.

Необходимо решить несколько проблем, в том числе:

  • Table использует Grid внутри для обеспечения виртуализированной прокрутки, но не показывает его в API везде, где это необходимо.
  • Grid имеет только один столбец, который содержит все Column ячеек в строке, но Grid передается как родительский для отрисовки Column ячеек. В результате одна Grid ячейка может быть связана со множеством Column ячеек, что не поддерживается Grid и CellMeasurer.
  • Использование CellMeasurer в 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
});

Для компонента Table:

  • добавить rowCache как deferredMeasurementCache опору
  • добавить функцию rowRenderer
  • добавить функцию rowHeight
  <Table
    ...
    deferredMeasurementCache={rowCache}
    rowRenderer={rowRenderer}
    rowHeight={rowHeight}
  >

Функции будут показаны позже. Table ничего не сделает с deferredMeasurementCache, кроме как передать его в качестве опоры Grid Table.

Для каждого 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 используется, чтобы открыть сетку таблицы для 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 не просто сообщает о высоте строки. Он также отвечает за перенос rowHeight строки в cellCache на высоту ячейки строки для первого и единственного столбца в rowCache.

Это решение можно несколько упростить, если в таблице есть только один измеряемый столбец. Нужен только один CellMeasurerCache. Один кэш может выполнять роль как cellCache, так и rowCache. Как результат:

  • Нет необходимости в cellParent; его можно удалить. Ссылки на cellParent могут быть заменены ссылками на rowParent, или, в случае функции measuredCellRenderer, свойство CellMeasurer parent может быть установлено непосредственно на аргумент функции parent.
  • Внутри measuredCellRenderer CellMeasurer необходимо жестко запрограммировать для columnIndex={0}, даже если измеряемый столбец не является первым столбцом в таблице.
  • Оператор if внутри функции rowHeight может быть удален, поскольку нет необходимости передавать высоту между двумя кешами.
  • aMeasuredColumnIndex можно удалить, так как он упоминается только в операторе rowHeight if.
person sudr minz    schedule 17.07.2019