react通过react-window实现虚拟列表

2023-06-19 下午前端 89 次浏览1条评论

VirtualTable.tsx

import { TableProps } from "antd";
import { Table } from "antd";
import classNames from "classnames";
import _ from "lodash";
import ResizeObserver from "rc-resize-observer";
import React, { ReactNode, useEffect, useRef, useState } from "react";
import { VariableSizeGrid as Grid } from "react-window";
type Props = {
  columns: any;
  dataSource: any[];
  scroll: Record<string, any>;
  loading?: boolean;
  tableTitle: () => ReactNode;
};
export const VirtualTable = (props: Props) => {
  const { columns, scroll, tableTitle, loading } = props;

  const [tableWidth, setTableWidth] = useState(0);

  const mergedColumns: any = columns!.map((column: any) => {
    if (column.width) {
      return column;
    }
    return {
      ...column,
      width: 200,
    };
  });

  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;
  });

  const resetVirtualGrid = () => {
    gridRef.current?.resetAfterIndices({
      columnIndex: 0,
      shouldForceUpdate: true,
    });
  };

  useEffect(() => resetVirtualGrid, [tableWidth]);

  const renderVirtualList: any = (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 as number) && 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 p-2 box-border", {
              "virtual-table-cell-last": columnIndex === mergedColumns.length - 1,
            })}
            style={{
              ...style,
            }}
          >
            {mergedColumns[columnIndex].render
              ? mergedColumns[columnIndex].render(
                  (rawData[rowIndex] as any)[(mergedColumns as any)[columnIndex].dataIndex],
                  rawData[rowIndex]
                )
              : (rawData[rowIndex] as any)[(mergedColumns as any)[columnIndex].dataIndex]}
          </div>
        )}
      </Grid>
    );
  };
  return (
    <ResizeObserver
      onResize={({ width }) => {
        setTableWidth(width);
      }}
    >
      <Table
        {...props}
        className="virtual-table"
        columns={mergedColumns}
        pagination={false}
        loading={loading}
        components={{
          body: renderVirtualList,
        }}
        title={tableTitle}
      />
    </ResizeObserver>
  );
};

使用

      <VirtualTable
        columns={columns}
        dataSource={list}
        scroll={{ y: document.body.clientHeight - 310, x: 2000 }}
        loading={isLoading}
        tableTitle={() => (
          <div className="flex justify-between w-full">
            <Pagination
              current={page}
              size="small"
              total={data?.total}
              showTotal={(total) => `共 ${total} 条`}
              pageSize={10000}
              showQuickJumper
              onChange={(page) => setPage(page)}
            />
            <div className="flex-auto flex justify-end items-center">
              <Button className="mr-2" type="primary" onClick={toggleCreateOpen}>
                新增数据
              </Button>
              <Input.Search
                placeholder="搜索当页数据"
                className="mr-1 w-48"
                allowClear
                onChange={_.debounce((e) => setKeywords(e.target.value), 300)}
              />
            </div>
          </div>
        )}
      />

目录

ICP备案号:鲁ICP备2020040322号