Изучение методов виртуализации на примере Ant Table

Зачем нужны виртуализированные таблицы?

Таблица, также называемая сеткой данных, представляет собой расположение данных в строках и столбцах или, возможно, в более сложной структуре. Это важный строительный блок пользовательского интерфейса. И в React Table, и в Ant Table мы предоставили примеры виртуализированных таблиц. Здесь мы используем Ant Table в качестве примера, чтобы углубиться в тему. Этот принцип можно применить и к другим компонентам таблицы.

Вот как использовать Create React App в качестве основы для создания настольного приложения:

% yarn create react-app react-virtual-data
% cd react-virtual-data

Установить два пакета

  • antd: Ant Design System — это открытый исходный код для языков дизайна пользовательского интерфейса корпоративного уровня и библиотеки пользовательского интерфейса React, которая включает в себя табличный компонент.
  • unique-names-generator: Это библиотека, которая генерирует уникальные и запоминающиеся строки имен.
% yarn add antd
% yarn add unique-names-generator

Они становятся частью dependencies в package.json:

"dependencies": {
  "antd": "^5.4.4",
  "unique-names-generator": "^4.7.1"
}

Создайте src/dataSource.js, который определяет столбцы таблицы и данные.

const {
  uniqueNamesGenerator,
  names,
  animals,
  colors,
} = require('unique-names-generator');

// table columns are defined as Name, Age, Gender, Grader, 
// Favorite Animal, and Favorite Color.
export const columns = [
  {
    title: 'Name',
    dataIndex: 'name',
    key: 'name',
  },
  {
    title: 'Age',
    dataIndex: 'age',
    key: 'age',
  },
  {
    title: 'Gender',
    dataIndex: 'gender',
    key: 'gender',
  },
  {
    title: 'Grade',
    dataIndex: 'grade',
    key: 'grade',
  },
  {
    title: 'Favorite Animal',
    dataIndex: 'animal',
    key: 'animal',
  },
  {
    title: 'Favorite Color',
    dataIndex: 'color',
    key: 'color',
  },
];


// generates row data with a given count
export const getData = (count) => {
  const data = [];
  for (let i = 0; i < count; i++) {
    const grade = Math.floor(Math.random() * 11) + 1;
    data[i] = {
      key: i,
      name: uniqueNamesGenerator({
        dictionaries: [names],
      }),
      age: grade + 5,
      gender: Math.random() > 0.5 ? 'F' : 'M',
      grade,
      animal: uniqueNamesGenerator({
        dictionaries: [animals],
      }),
      color: uniqueNamesGenerator({
        dictionaries: [colors],
      }),
    };
  }
  return data;
};

Измените src/App.js, чтобы создать таблицу, используя columns и getData из dataSource, где количество строк таблицы равно 100:

import { Table } from 'antd';
import { columns, getData } from './dataSource';

function App() {
  return (
    <Table columns={columns} pagination={false} dataSource={getData(100)} />
  );
}

export default App;

Выполняем yarn start, и видим таблицу с вертикальной полосой прокрутки.

  • При наличии 100 строк таблица инициализируется быстро.
  • Измените таблицу на 1000 строк, и производительность останется хорошей.
  • Измените количество на 10 000, и таблица появится через несколько секунд.

  • Измените количество на 100 000, и приложение даже не отвечает.

Независимо от вычислительной мощности и эффективности алгоритма существует ограничение на количество узлов DOM, которые должны быть отображены в течение воспринимаемой продолжительности. Существует метод, известный как работа с окнами (виртуализация), чтобы помочь в этой ситуации. Он отображает только небольшое подмножество данных в любой момент времени, что может значительно сократить время, необходимое для повторного рендеринга компонентов, и количество создаваемых узлов DOM.

Как мы можем виртуализировать таблицы?

Существуют популярные библиотеки виртуализации, такие как react-window, react-virtualized и react-virtuoso. Предыдущая статья предоставила примеры для отображения виртуализированных таблиц с использованием Table react-virtualized и TableVirtuoso react-virtuoso. Это простые таблицы, ограниченные и ограниченные, и они могут не соответствовать визуальным требованиям для корпоративного проекта.

Скорее всего, у проекта есть своя система дизайна, включая брендированные столы. Общие List или Grid могут обеспечить большую гибкость для существующих таблиц. React Table помещает FixedSizeList непосредственно под tbody, а Ant Table может использовать VariableSizeGrid внутри components.body.

Вот таблица Ant TableProps, чье дополнительное свойство components можно использовать для настройки элементов таблицы.

export interface TableProps<RecordType>{
  columns?: ColumnsType<RecordType>;
  pagination?: false | TablePaginationConfig;
  dataSource?: RcTableProps<RecordType>['data'];
  components?: TableComponents<RecordType>;
  ...
}

TableComponents может настроить table, header и/или body.

export interface TableComponents<RecordType> {
  table?: CustomizeComponent;
  header?: {
    wrapper?: CustomizeComponent;
    row?: CustomizeComponent;
    cell?: CustomizeComponent;
  };
  body?: CustomizeScrollBody<RecordType> | {
    wrapper?: CustomizeComponent;
    row?: CustomizeComponent;
    cell?: CustomizeComponent;
  };
}

А CustomizeScrollBody — это компонент с параметрами необработанной data и информации о прокрутке (scrollbarSize, ref и onScroll).

export declare type CustomizeScrollBody<RecordType> = (data: readonly RecordType[], info: {
    scrollbarSize: number;
    ref: React.Ref<{
        scrollLeft: number;
    }>;
    onScroll: (info: {
        currentTarget?: HTMLElement;
        scrollLeft?: number;
    }) => void;
}) => React.ReactNode;

Помимо общих библиотек виртуализации, существуют специальные библиотеки, созданные для виртуализации таблиц Ant, такие как virtualized-table-for-antd и virtuallist-antd.

Мы собираемся изучить пять способов виртуализации таблиц Ant. Мы оцениваем каждую библиотеку по следующим пяти критериям:

  • Размер в сжатом виде: сколько дополнительного места
  • Емкость данных: количество строк, которые можно загрузить в таблицу с приемлемой производительностью.
  • Побочные эффекты: Есть ли нежелательное поведение
  • Строка кода: номер строки, включая фактический код, комментарии и интервалы, как приблизительное указание сложности реализации.
  • Изменение размера: как виртуализированная таблица реагирует на изменение ширины таблицы

Использование React-Window

react-window — это набор компонентов React для эффективного рендеринга больших списков и табличных данных. Он отображает часть большого набора данных, достаточного, чтобы заполнить область просмотра:

  • Это сокращает работу и время, необходимые для визуализации исходного вида и обработки обновлений.
  • Это уменьшает объем памяти, избегая чрезмерного выделения узлов DOM.

Установите пакет, react-window

% yarn add react-window

Он становится частью dependencies в package.json

"dependencies": {
  "react-window": "^1.8.9"
}

react-window — небольшая библиотека. Согласно https://bundlephobia.com/, его размер в сжатом виде составляет 6,4 КБ. По умолчанию react-window состоит из четырех компонентов: FixedSizeList, VariableSizeList, FixedSizeGrid и VariableSizeGrid.

react-window — это официальный пример, предоставленный Ant Design System. Когда components.body используется с реквизитами рендеринга, каждый столбец должен иметь фиксированное значение width. Вот почему мы создаем mergedColumns с указанными значениями width.

VariableSizeGrid используется для таблицы строк фиксированного размера, потому что ее columnWidth — это функция, обеспечивающая гибкость.

Работа с ResizeObserver не обязательна, но помогает показать, как виртуальная таблица приспосабливается к динамической ширине таблицы. Метод resetAfterIndices VariableSizeGrid очищает кэшированные данные, чтобы обеспечить синхронизацию заголовка и тела таблицы при изменении размера.

Следующий измененный src/App.js показывает, как работает виртуальная таблица с использованием react-window.

import { useEffect, useRef, useState } from 'react';
import { VariableSizeGrid as Grid } from 'react-window';
import ResizeObserver from 'rc-resize-observer';
import { Table } from 'antd';
import { columns, getData } from './dataSource';
import './App.css';

/**
 * Create an Ant table that is wrapped inside ResizeObserver
 * @param {Object} props takes Table props
 * @returns Table Component
 */
function VirtualTable(props) {
  const { columns, scroll } = props;
  const [tableWidth, setTableWidth] = useState(0);

  // the column width is set to be the equal size
  // '+ 100' is a use case to generate a horizontal scroll
  const width = Math.floor(tableWidth / columns.length)/* + 100 */;

  // each column is assigned the width prop as required by components.body
  const mergedColumns = columns.map((column) => ({
    ...column,
    width,
  }));

  // a ref of the table
  const gridRef = useRef();

  // clear cached data for all items, starting from column 0
  const resetVirtualGrid = () => {
    gridRef.current?.resetAfterIndices({
      columnIndex: 0,
      shouldForceUpdate: true,
    });
  };

  // clear cached data after tableWidth changes
  useEffect(() => resetVirtualGrid, [tableWidth]);

  // generate the table body for Ant table
  const renderVirtualList = (
    rawData,
    { scrollbarSize, /* ref, */ onScroll }
  ) => {
    const totalHeight = rawData.length * 54; // cell height is 54

    return (
      // render each cell for proper width and height
      <Grid
        ref={gridRef}
        columnCount={mergedColumns.length}
        columnWidth={(index) => {
          const { width } = mergedColumns[index]; 
          // the last column needs to subtract scrollbar size, if there is a vertical scroll
          return totalHeight > scroll.y && index === mergedColumns.length - 1
            ? width - scrollbarSize - 1
            : width;
        }}
        height={scroll.y} // 650
        rowCount={rawData.length}
        rowHeight={() => 54}
        width={tableWidth}
        // needed to scroll header and body together when there is a horizontal scroll
        onScroll={({ scrollLeft }) => {
          onScroll({
            scrollLeft,
          });
        }}
      >
        {/* use render props to render each cell */}
        {({ columnIndex, rowIndex, style }) => (
          // sets the virtual-table-cell class for styling
          <div className="virtual-table-cell" style={style}>
            {/* retrieve the cell data for rowIndex and columnIndex */}
            {rawData[rowIndex][mergedColumns[columnIndex].dataIndex]}
          </div>
        )}
      </Grid>
    );
  };

  return (
    // Ant table that is wrapped inside ResizeObserver
    <ResizeObserver
      onResize={({ width }) => {
        // adjust the table width, if the browser resizes
        setTableWidth(width);
      }}
    >
      <Table
        {...props}
        columns={mergedColumns}
        pagination={false}
        components={{
          body: renderVirtualList, // render the table body
        }}
      />
    </ResizeObserver>
  );
}

function App() {
  return (
    // create the VirtualTable
    <VirtualTable
      columns={columns} // column definition
      dataSource={getData(100000)} // row data
      scroll={{
        y: 650, // the table height
      }}
    />
  );
}

export default App;

Чтобы сделать таблицу похожей на таблицу Ant, src/App.css изменяется следующим образом:

.virtual-table-cell {
  box-sizing: border-box;
  padding: 16px;
  border-bottom: 1px solid #e8e8e8;
}

.ant-table {
  border-bottom: 2px solid #e8e8e8;
}

Выполните yarn start, и мы увидим, что таблица загружается достаточно быстро для 100 000 строк.

Измените количество на 1 000 000. Таблица отображается через несколько секунд, но она все еще работает.

В приведенном выше коде включите следующую строку:

const width = Math.floor(tableWidth / columns.length) + 100;

Он генерирует горизонтальную и вертикальную прокрутку, и обе прокрутки работают хорошо и эффективно.

Изменение размера отзывчивое и плавное.

Вот оценочная карта react-window:

  • Размер в сжатом виде: 6,4 КБ
  • Емкость данных: 1 000 000 строк
  • Побочные эффекты: Нет
  • Строка кода: 116
  • Изменение размера: хорошо работает с ResizeObserver, а изменение размера является гибким и плавным.

Использование React-Virtualized

react-virtualized также представляет собой набор компонентов React для эффективного рендеринга больших списков и табличных данных. Он отображает часть большого набора данных, достаточного для заполнения окна просмотра.

Установите пакет, react-virtualized

% yarn add react-virtualized

Он становится частью dependencies в package.json

"dependencies": {
  "react-virtualized": "^9.22.5"
}

react-virtualized является прецедентом react-window. Он громоздкий с полным функционалом. Согласно https://bundlephobia.com/, его размер в сжатом виде составляет 27,3 КБ. По умолчанию react-virtualized имеет пять основных компонентов, Collection, Grid, List, Masonry и Table, а также восемь компонентов высокого порядка, которые помогают или украшают основные компоненты, AutoSizer, ArrowKeyStepper, CellMeasurer, ColumnSizer, InfiniteLoader, MultiGrid, ScrollSync и WindowScroller.

react-virtualized похож на react-window, за исключением нескольких небольших отличий:

  • Используйте recomputeGridSize для очистки кэшированных данных для всех элементов вместо resetAfterIndices.
  • itemContent — средство визуализации строк вместо средства визуализации ячеек.
  • cellRenderer - реквизит вместо children.

Следующий src/App.js виртуализирует таблицу Ant, используя Grid. Он использует ResizeObserver для настройки ширины таблицы при изменении размера браузера.

import { useEffect, useRef, useState } from 'react';
import { Grid } from 'react-virtualized';
import ResizeObserver from 'rc-resize-observer';
import { Table } from 'antd';
import { columns, getData } from './dataSource';
import './App.css';

/**
 * Create an Ant table that is wrapped inside ResizeObserver
 * @param {Object} props takes Table props
 * @returns Table Component
 */
function VirtualTable(props) {
  const { columns, scroll } = props;
  // a none-zero value to show correct header during refresh
  const [tableWidth, setTableWidth] = useState(1);

  // the column width is set to be the equal size
  // '+ 100' is a use case to generate a horizontal scroll
  const width = Math.floor(tableWidth / columns.length)/* + 100 */;

  // each column is assigned the width prop as required by components.body
  const mergedColumns = columns.map((column) => ({
    ...column,
    width,
  }));

  // a ref of the table
  const gridRef = useRef();

  // clear cached data for all items, starting from the top left cell
  const resetVirtualGrid = () => {
    gridRef.current?.recomputeGridSize({ columnIndex: 0, rowIndex: 0 });
  };

  // clear cached data after tableWidth changes
  useEffect(() => resetVirtualGrid, [tableWidth]);

  // generate the table body for Ant table
  const renderVirtualList = (
    rawData,
    { scrollbarSize, /* ref, */ onScroll }
  ) => {
    const totalHeight = rawData.length * 54; // cell height is 54

    return (
      // render each cell for proper width and height
      <Grid
        ref={gridRef}
        // call cell renderer to render each cell
        cellRenderer={({ columnIndex, rowIndex, style }) => (
          <div className="virtual-table-cell" style={style}>
            {rawData[rowIndex][mergedColumns[columnIndex].dataIndex]}
          </div>
        )}
        columnCount={mergedColumns.length}
        columnWidth={({ index }) => {
          const { width } = mergedColumns[index];
          // the last column needs to subtract scrollbar size, if there is a vertical scroll
          return totalHeight > scroll.y && index === mergedColumns.length - 1
            ? width - scrollbarSize - 1
            : width;
        }}
        height={scroll.y} // 650
        rowCount={rawData.length}
        rowHeight={() => 54}
        width={tableWidth}
        // needed to scroll header and body together when there is a horizontal scroll
        onScroll={({ scrollLeft }) => {
          onScroll({
            scrollLeft,
          });
        }}
      />
    );
  };

  return (
    // Ant table that is wrapped inside ResizeObserver
    <ResizeObserver
      onResize={({ width }) => {
        // adjust the table width, if the browser resizes
        setTableWidth(width);
      }}
    >
      <Table
        {...props}
        columns={mergedColumns}
        pagination={false}
        components={{
          body: renderVirtualList, // render the table body
        }}
      />
    </ResizeObserver>
  );
}

function App() {
  return (
    // create the VirtualTable
    <VirtualTable
      columns={columns} // column definition
      dataSource={getData(1000000)} // row data
      scroll={{
        y: 650, // the table height
      }}
    />
  );
}

export default App;

Выполнить yarn start. Загрузка 1 000 000 строк занимает несколько секунд, но все равно работает.

В приведенном выше коде включите следующую строку:

const width = Math.floor(tableWidth / columns.length) + 100;

Он генерирует горизонтальную и вертикальную прокрутку, и обе прокрутки работают хорошо и эффективно.

Изменение размера отзывчивое и плавное.

Вот оценочная карта react-virtualized:

  • Размер в сжатом виде: 27,3 КБ
  • Емкость данных: 1 000 000 строк
  • Побочные эффекты: Нет
  • Строка кода: 111
  • Изменение размера: хорошо работает с ResizeObserver, изменение размера происходит плавно и плавно.

Использование React-Virtuoso

react-virtuoso — это набор компонентов React, которые могут отображать огромные наборы данных.

Установите пакет, react-virtuoso

% yarn add react-virtuoso

Он становится частью dependencies в package.json

"dependencies": {
  "react-virtuoso": "^4.3.1"
}

react-virtuoso — это библиотека среднего размера. Согласно https://bundlephobia.com/, его размер в сжатом виде составляет 16,3 КБ. По умолчанию react-window состоит из четырех основных компонентов: Virtuoso, GroupedVirtuoso, TableVirtuoso и VirtuosoGrid.

react-virtuoso похож на react-window или react-virtualized, за исключением нескольких отличий:

  • Не существует метода, подобного resetAfterIndices или recomputeGridSize, который очищает кэшированные данные для всех элементов. gridRef удаляется вместе с блоком useEffect для очистки кэшированных данных при tableWidth изменениях.
  • useWindowScroll упрощает код прокрутки (нет необходимости вычитать размер полосы прокрутки).
  • itemContent используется для рендеринга каждой строки.

Следующий src/App.js виртуализирует таблицу Ant с помощью TableVirtuoso. Он использует ResizeObserver для настройки ширины таблицы при изменении размера браузера.

import { useState } from 'react';
import { TableVirtuoso } from 'react-virtuoso';
import ResizeObserver from 'rc-resize-observer';
import { Table } from 'antd';
import { columns, getData } from './dataSource';
import './App.css';

/**
 * Create an Ant table that is wrapped inside ResizeObserver
 * @param {Object} props takes Table props
 * @returns Table Component
 */
function VirtualTable(props) {
  const { columns } = props;
  const [tableWidth, setTableWidth] = useState(0);

  // the column width is set to be the equal size
  // '+ 100' is a use case to generate a horizontal scroll
  const width = Math.floor(tableWidth / columns.length); /* + 100 */

  // each column is assigned the width prop as required by components.body
  const mergedColumns = columns.map((column) => ({
    ...column,
    width,
  }));

  // generate the table body for Ant table
  const renderVirtualList = (
    rawData
    /* { scrollbarSize, ref, onScroll } */
  ) => (
    // render each row for proper width, and the height is managed by useWindowScroll
    <TableVirtuoso
      data={rawData}
      useWindowScroll
      itemContent={(rowIndex, item) =>
        columns.map((col, colIndex) => {
          const { width } = mergedColumns[colIndex];
          return (
            <td
              className="virtual-table-cell"
              key={col.dataIndex}
              style={{
                minWidth: `${width}px`,
                height: 35,
              }}
            >
              {item[col.dataIndex]}
            </td>
          );
        })
      }
    />
  );

  return (
    // Ant table that is wrapped inside ResizeObserver
    <ResizeObserver
      onResize={({ width }) => {
        // adjust the table width, if the browser resizes
        setTableWidth(width);
      }}
    >
      <Table
        {...props}
        columns={mergedColumns}
        pagination={false}
        components={{
          body: renderVirtualList, // render the table body
        }}
      />
    </ResizeObserver>
  );
}

function App() {
  return (
    // create the VirtualTable
    <VirtualTable
      columns={columns} // column definition
      dataSource={getData(1000000)} // row data
      scroll={{
        // the table height, required by components.body
        // even it is not needed with useWindowScroll is set
        y: 650,
      }}
    />
  );
}

export default App;

Выполнить yarn start. Загрузка 1 000 000 строк занимает несколько секунд, но все равно работает.

В приведенном выше коде включите следующую строку:

const width = Math.floor(tableWidth / columns.length) + 100;

Он генерирует горизонтальную и вертикальную прокрутку. Прокрутите вправо, и заголовок был обрезан.

Это вызвано тем, что не очищаются кэшированные данные для всех элементов.

Измените src/App.css, чтобы заставить .ant-table-header отображать overflow:

.virtual-table-cell {
  box-sizing: border-box;
  padding: 16px;
  border-bottom: 1px solid #e8e8e8;
}

.ant-table {
  border-bottom: 2px solid #e8e8e8;
}

.ant-table-header {
  overflow: visible !important;
}

Теперь обе прокрутки работают хорошо и эффективно.

Изменение размера отзывчивое и плавное.

Вот оценочная карта react-virtuoso:

  • Размер в сжатом виде: 16,3 КБ
  • Емкость данных: 1 000 000 строк
  • Побочные эффекты: Нет
  • Строка кода: 91
  • Изменение размера: работает с ResizeObserver, но требуется хак, чтобы заголовок не обрезался. Изменение размера отзывчивое и плавное.

Использование Virtualized-Table-For-Antd

virtualized-table-for-antd — это компонент виртуализированной таблицы для Ant Design System. Это конкретная реализация без использования какой-либо библиотеки виртуализации. Хотя пакет называется virtualizedtableforantd4, он работает как для antd 4, так и для antd 5.

Установите пакет, virtualizedtableforantd4

% yarn add virtualizedtableforantd4

Он становится частью dependencies в package.json

"dependencies": {
  "virtualizedtableforantd4": "^1.3.1"
}

Согласно https://snyk.io/advisor/npm-package/virtualizedtableforantd4, его установленный размер составляет 63,9 КБ.

Следующий src/App.js вызывает useVT для создания виртуализированного компонента таблицы, используемого в качестве компонента таблицы Ant. Он использует ResizeObserver для настройки ширины таблицы при изменении размера браузера.

import { useState } from 'react';
import { useVT } from 'virtualizedtableforantd4';
import ResizeObserver from 'rc-resize-observer';
import { Table } from 'antd';
import { columns, getData } from './dataSource';
import './App.css';

/**
 * Create an Ant table that is wrapped inside ResizeObserver
 * @param {Object} props takes Table props
 * @returns Table Component
 */
function VirtualTable(props) {
  const { columns, scroll, dataSource } = props;
  const [tableWidth, setTableWidth] = useState(0);
  // create a virtual table with specific height
  const [vt] = useVT(() => ({ scroll: { y: scroll.y } }), []);

  const totalHeight = dataSource.length * 54; // cell height is 54

  // each column is assigned the width prop
  // the column width is set to be the equal size, except the last column
  const width = Math.floor(tableWidth / columns.length);
  const mergedColumns = columns.map((column, index) => ({
    ...column,
    // width
    width: totalHeight > scroll.y && index === columns.length - 1
    ? Math.max([0, width - 25])
    : width,
  }));

  return (
    // Ant table that is wrapped inside ResizeObserver
    <ResizeObserver
      onResize={({ width }) => {
        // adjust the table width, if the browser resizes
        setTableWidth(width);
      }}
    >
      <Table
        {...props}
        columns={mergedColumns}
        dataSource={dataSource}
        pagination={false}
        components={vt} // render the whole virtual table
      />
    </ResizeObserver>
  );
}

function App() {
  return (
    // create the VirtualTable
    <VirtualTable
      columns={columns} // column definition
      dataSource={getData(250000)} // row data
      scroll={{
        y: 650, // the table height
      }}
    />
  );
}

export default App;

Выполнить yarn start. Загрузка 250 000 строк занимает несколько секунд. Во время загрузки отображается некоторый промежуточный контент.

При изменении размера браузера реакция медленная.

Однако такой автономный пакет не предназначен для работы с ResizeObserver.

Избавившись от ResizeObserver, использовать useVT довольно просто.

import { useVT } from 'virtualizedtableforantd4';
import { Table } from 'antd';
import { columns, getData } from './dataSource';
import './App.css';

/**
 * Create an Ant table that is wrapped inside ResizeObserver
 * @param {Object} props takes Table props
 * @returns Table Component
 */
function VirtualTable(props) {
  const { columns, scroll, dataSource } = props;
  // create a virtual table with specific height
  const [vt] = useVT(() => ({ scroll: { y: scroll.y } }), []);

  return (
      <Table
        {...props}
        columns={columns}
        dataSource={dataSource}
        pagination={false}
        components={vt} // render the whole virtual table
      />
  );
}

function App() {
  return (
    // create the VirtualTable
    <VirtualTable
      columns={columns} // column definition
      dataSource={getData(250000)} // row data
      scroll={{
        y: 650, // the table height
      }}
    />
  );
}

export default App;

Выполнить yarn start. Загрузка 250 000 строк занимает несколько секунд. Во время загрузки все еще отображается некоторый промежуточный контент.

Тем не менее, изменение размера намного более отзывчиво.

Вот оценочная карта react-virtualized:

  • Установленный размер: 63,9 КБ
  • Емкость данных: 250 000 строк
  • Побочные эффекты: отображение промежуточного контента
  • Строка кода: 40
  • Изменение размера: Плохо работает с ResizeObserver. Его собственное изменение размера является отзывчивым и плавным.

Использование Virtuallist-Antd

virtuallist-antd — еще один виртуализированный табличный компонент для Ant Design System. Это конкретная реализация без использования какой-либо библиотеки виртуализации. Этот пакет работает как для antd 4, так и для antd 5.

Установите пакет, virtuallist-antd

% yarn add virtuallist-antd

Он становится частью dependencies в package.json

"dependencies": {
  "virtuallist-antd": "^0.7.6"
}

virtuallist-antd — небольшая библиотека. Согласно https://bundlephobia.com/, его размер в сжатом виде составляет 3,6 КБ.

Следующий src/App.js вызывает VList для создания виртуализированного компонента таблицы, используемого в качестве компонента таблицы Ant. Он использует ResizeObserver для настройки ширины таблицы при изменении размера браузера.

import { useMemo, useState } from 'react';
import { VList } from 'virtuallist-antd';
import ResizeObserver from 'rc-resize-observer';
import { Table } from 'antd';
import { columns, getData } from './dataSource';
import './App.css';

/**
 * Create an Ant table that is wrapped inside ResizeObserver
 * @param {Object} props takes Table props
 * @returns Table Component
 */
function VirtualTable(props) {
  const { columns, scroll, dataSource } = props;
  const [tableWidth, setTableWidth] = useState(0);

  const totalHeight = dataSource.length * 54; // cell height is 54

  // each column is assigned the width prop
  // the column width is set to be the equal size, except the last column
  const width = Math.floor(tableWidth / columns.length);
  const mergedColumns = columns.map((column, index) => ({
    ...column,
    // width
    width: totalHeight > scroll.y && index === columns.length - 1
    ? Math.max([0, width - 25])
    : width,
  }));
  
  // useMemo improves loading performance
  const renderVirtualList = useMemo(() => {
    return VList({
      height: scroll.y,
    });
  }, [scroll.y]);

  return (
    // Ant table that is wrapped inside ResizeObserver
    <ResizeObserver
      onResize={({ width }) => {
        // adjust the table width, if the browser resizes
        setTableWidth(width);
      }}
    >
      <Table
        {...props}
        columns={mergedColumns}
        dataSource={dataSource}
        pagination={false}
        components={renderVirtualList} // render the whole virtual table
      />
    </ResizeObserver>
  );
}

function App() {
  return (
    // create the VirtualTable
    <VirtualTable
      columns={columns} // column definition
      dataSource={getData(250000)} // row data
      scroll={{
        y: 650, // the table height
      }}
    />
  );
}

export default App;

Выполнить yarn start. Загрузка 250 000 строк занимает несколько секунд. Во время загрузки отображается некоторый промежуточный контент.

При изменении размера браузера отзывчивость не плавная.

Однако такой автономный пакет не предназначен для работы с ResizeObserver.

Избавившись от ResizeObserver, использовать VList довольно просто.

import { useMemo } from 'react';
import { VList } from 'virtuallist-antd';
import { Table } from 'antd';
import { columns, getData } from './dataSource';
import './App.css';

/**
 * Create an Ant table that is wrapped inside ResizeObserver
 * @param {Object} props takes Table props
 * @returns Table Component
 */
function VirtualTable(props) {
  const { columns, scroll, dataSource } = props;

  // useMemo improves loading performance
  const renderVirtualList = useMemo(() => {
    return VList({
      height: scroll.y,
    });
  }, [scroll.y]);

  return (
    <Table
      {...props}
      columns={columns}
      dataSource={dataSource}
      pagination={false}
      components={renderVirtualList} // render the whole virtual table
    />
  );
}

function App() {
  return (
    // create the VirtualTable
    <VirtualTable
      columns={columns} // column definition
      dataSource={getData(250000)} // row data
      scroll={{
        y: 650, // the table height
      }}
    />
  );
}

export default App;

Выполнить yarn start. Загрузка 250 000 строк занимает несколько секунд. Во время загрузки все еще отображается некоторый промежуточный контент.

Тем не менее, изменение размера намного более отзывчиво.

Вот оценочная карта virtuallist-antd:

  • Размер в сжатом виде: 3,6 КБ
  • Емкость данных: 250 000 строк
  • Побочные эффекты: отображение промежуточного контента
  • Строка кода: 46
  • Изменение размера: Плохо работает с ResizeObserver. Его собственное изменение размера является отзывчивым и плавным.

Заключение

Мы рассмотрели методы виртуализации на примере таблицы Ant с популярными библиотеками виртуализации react-window, react-virtualized и react-virtuoso, а также специальными библиотеками, созданными для виртуализации таблиц Ant, virtualized-table-for-antd и virtuallist-antd.

Вот окончательные оценочные листы:

окно реакции

  • Размер в сжатом виде: 6,4 КБ
  • Емкость данных: 1 000 000 строк
  • Побочные эффекты: Нет
  • Строка кода: 46
  • Изменение размера: хорошо работает с ResizeObserver, а изменение размера является гибким и плавным.

реактивно-виртуализированный

  • Размер в сжатом виде: 27,3 КБ
  • Емкость данных: 1 000 000 строк
  • Побочные эффекты: Нет
  • Строка кода: 111
  • Изменение размера: хорошо работает с ResizeObserver, а изменение размера является гибким и плавным.

виртуоз реакции

  • Размер в сжатом виде: 16,3 КБ
  • Емкость данных: 1 000 000 строк
  • Побочные эффекты: Нет
  • Строка кода: 91
  • Изменение размера: работает с ResizeObserver, но требуется хак, чтобы показать часть переполнения заголовка таблицы. Изменение размера отзывчивое и плавное.

virtualized-table-for-antd

  • Установленный размер: 63,9 КБ
  • Емкость данных: 250 000 строк
  • Побочные эффекты: отображение промежуточного контента
  • Строка кода: 40
  • Изменение размера: Плохо работает с ResizeObserver. Его собственное изменение размера является отзывчивым и плавным.

виртуальный список-antd

  • Размер в сжатом виде: 3,6 КБ
  • Емкость данных: 250 000 строк
  • Побочные эффекты: отображение промежуточного контента
  • Строка кода: 46
  • Изменение размера: Плохо работает с ResizeObserver. Его собственное изменение размера является отзывчивым и плавным.

Мы показали пять способов улучшить производительность таблицы. react-window — наш выбор, аналогичный рекомендациям React Table и Ant Table.

Спасибо за прочтение.

Спасибо С. Шрираму и Дургадеви Сирипурапу за работу со мной над продуктами Domino.

Want to Connect?

If you are interested, check out my directory of web development articles.