react-window实践表格纵向虚拟滚动与所遇问题记录

1,935 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第6天,点击查看活动详情

大数据量表格渲染性能问题

我们都知道在React或其他项目中,当需要渲染非常多数据的表格时,都会遇到渲染性能问题,导致页面卡断白屏等,一般解决大数据量列表的方式都是虚拟列表,而大数据量的表格则会采取分页的形式或者虚拟滚动的形式解决渲染性能问题,本文就记录一下使用react-window实现纵向虚拟滚动表格时的过程,与实践中所遇到的问题。

react-window实现表格纵向虚拟滚动

像Antd为了防止当有成千上万行数据时出现的表格性能问题,就提供了react-window来实现虚拟滚动。首先需要安装这个库:
yarn add react-window
当将react-window与表格Table结合时,需要使用Grid,即需要从中引入来实现虚拟网格:
import { VariableSizeGrid as Grid } from 'react-window';
为了便于用在不同地方和多个表格都能实现虚拟滚动,将其封装为一个单独的组件ReactWindowComp,由于要用到表格和滚动,所以可以将数据源dataSource、表格列columns和表格滚动区域的宽高限制scroll从父组件的props里解构出来,并根据总列数计算所有列的自适应宽度,重新设置columns:

  const { columns, scroll } = props;
  const [tableWidth, setTableWidth] = useState(0);

  const widthColumnCount = columns!.filter(({ width }: any) => !width).length;
  const mergedColumns = columns!.map((column: any) => {
    if (column.width) {
      return column;
    }

    return {
      ...column,
      width: Math.floor(tableWidth / widthColumnCount),
    };
  });

接下来,提供useRef定义一个ref对象gridRef,用于承载当前需要虚拟滚动的表格内容,并且定义一个connectObject对象,使用Object.defineProperty将该connectObject对象添加scrollLeft属性,为其提供获取和设置scrollLeft值的方法,如下代码:

  const gridRef = useRef<any>();
  const [connectObject] = useState<any>(() => {
    const obj = {};
    Object.defineProperty(obj, 'scrollLeft', {
      get: () => {
        if (gridRef.current) {
          return gridRef.current?.state?.scrollLeft;
        }
        return null;
      },
      set: (scrollLeft: number) => {
        if (gridRef.current) {
          gridRef.current.scrollTo({ scrollLeft });
        }
      },
    });

    return obj;
  });

之后就可以定义renderVirtualList组件,渲染基本内容了,首先提供引入的VariableSizeGrid虚拟网格,将表格相关属性(ref、columnCount、columnWidth、height、rowCount、rowHeight、width和onScroll方法)传入进虚拟网格Grid中,将表格单元格内容给渲染出来:

  const renderVirtualList = (
    rawData: object[],
    { scrollbarSize, ref, onScroll }: any
  ) => {
    ref.current = connectObject;
    const totalHeight = rawData.length * 54;

    return (
      <Grid
        ref={gridRef}
        className="virtual-grid"
        columnCount={mergedColumns.length}
        columnWidth={(index: number) => {
          const { width } = mergedColumns[index];
          return totalHeight > scroll!.y! && index === mergedColumns.length - 1
            ? (width as number) - scrollbarSize - 1
            : (width as number);
        }}
        height={scroll!.y as number}
        rowCount={rawData.length}
        rowHeight={() => 54}
        width={tableWidth}
        onScroll={({ scrollLeft }: { scrollLeft: number }) => {
          onScroll({ scrollLeft });
        }}
      >
        {({
          columnIndex,
          rowIndex,
          style,
        }: {
          columnIndex: number;
          rowIndex: number;
          style: React.CSSProperties;
        }) => (
          <div
            className={classNames('virtual-table-cell', {
              'virtual-table-cell-last':
                columnIndex === mergedColumns.length - 1,
            })}
            style={style}
          >
            {
              (rawData[rowIndex] as any)[
                (mergedColumns as any)[columnIndex].dataIndex
              ]
            }
          </div>
        )}
      </Grid>
    );
  };

还需要注意的是,当react-window与Table结合使用时,应该引入rc-resize-observer库,能够自适应表格中宽度的变化:
import ResizeObserver from 'rc-resize-observer';
最后只需要用包裹虚拟网格列表renderVirtualList组件,到这一步,封装虚拟滚动表格的组件就完成了:

  return (
    <ResizeObserver
      onResize={({ width }) => {
        setTableWidth(width);
      }}
    >
      <Table
        {...props}
        className="virtual-table"
        columns={mergedColumns}
        pagination={false}
        components={{
          body: renderVirtualList,
        }}
      />
    </ResizeObserver>
  );

使用该ReactWindowComp封装组件就可以轻松实现一个1000行数据的表格,代码如下:

    <ReactWindowComp
      scroll={{ y: 300, x: 800 }}
      dataSource={data}
      columns={columns}
    />

渲染得到的效果如下:

image.png

react-window实现虚拟滚动表格存在的问题

  • react-window虽然可以解决当表格行数较多时的纵向虚拟滚动,但是当表格列数较多时,表头也会非常多,无法实现表头的横向虚拟滚动
  • react-window对于较复杂的表格不适应,比如需要实现表头分组或左侧折叠与展开等功能的大数据量表格时,react-window就不适用了,它不能满足这些复杂功能实现。
    如下效果,无法渲染表头分组的内容:

image.png 解决方式:
下一篇文章我会介绍并利用ali-react-table这个库,来实现大数据量表格的横向虚拟滚动、纵向虚拟滚动、表头横向虚拟滚动。