react-window构造的虚拟列表使用react-resizable动态调整宽度和使用react-drag-listview拖拽变换列位置的问题

react-window构造的虚拟列表使用react-resizable动态调整宽度和使用react-drag-listview拖拽变换列位置的问题

需求

项目中使用react-windowVariableSizeGrid构造虚拟列表来解决大型的数据列表渲染的性能问题。虚拟列表的优点是不用全部加载出所有的DOM节点, 而是按需显示思路的一种实现,即虚拟列表是一种根据滚动容器元素的可视区域来渲染长列表数据中某一个部分数据的技术。具体关于虚拟列表的使用和原理这里就不过多赘述了。

ant design官网中就是使用react-window去构造虚拟列表的

在这里插入图片描述

然后我们的需求是,想要react-resizable去动态调整table(虚拟列表)的列宽度,还有使用react-drag-listview拖拽变换列位置。关于具体实现,这里不过多介绍,网上有很多参考的例子:Ant Design + react-drag-listview实现Table拖拽变换列位置

问题

然而在实际开发中发现,对于普通Table(非虚拟列表)是生效的,能够动态的改变列的宽度和位置,然而对虚拟列表却无法变化。

问题根源

VariableSizeGrid 会遇到虚拟列表项样式缓存没有被清除导致和第一次可视区域里展示的一样。

我们可以看到VariableSizeGrid中的两个API

 /**
     * VariableSizeGrid caches offsets and measurements for each column index for performance purposes.
     * This method clears that cached data for all columns after (and including) the specified index.
     * It should be called whenever a column's width changes. (Note that this is not a typical occurrence.)
     *
     * By default the grid will automatically re-render after the index is reset.
     * If you would like to delay this re-render until e.g. a state update has completed in the parent component,
     * specify a value of false for the second, optional parameter.
     */
    resetAfterColumnIndex(index: number, shouldForceUpdate?: boolean): void;
    
     /**
     * VariableSizeGrid caches offsets and measurements for each row index for performance purposes.
     * This method clears that cached data for all rows after (and including) the specified index.
     * It should be called whenever a row's height changes. (Note that this is not a typical occurrence.)
     *
     * By default the grid will automatically re-render after the index is reset.
     * If you would like to delay this re-render until e.g. a state update has completed in the parent component,
     * specify a value of false for the second, optional parameter.
     */
    resetAfterRowIndex(index: number, shouldForceUpdate?: boolean): void;

大概意思就是出于性能优化的目的,VariableSizeGrid会缓存列表的行高和列框, 所以当我们调整了列的宽度,但是却没有清楚掉这些缓存,就会导致虚拟列表不会渲染出来最新的样式。

所以我们可以手动调用这两个API来达到动态调整宽度和变化列位置。

部分代码

其中核心代码是下面这句

const refreshVirtualTable = ()=>{
    if (gridRef?.current) {
      (gridRef.current as any)?.resetAfterRowIndex(0);
      (gridRef.current as any)?.resetAfterColumnIndex(0);
    }
  }

下面给出部分代码

import React, {useEffect, useLayoutEffect, useRef, useState} from 'react';
import 'antd/dist/antd.css';
import './index.css';
import {VariableSizeGrid as Grid} from 'react-window';
import classNames from 'classnames';
import ResizeObserver from 'rc-resize-observer';
import DragTable from "@/components/common/DragTable";

function VirtualTable(props: any) {
  const normalRowHeight = 25;
  const {columns, scroll, rowHeight} = props;
  const [tableWidth, setTableWidth] = useState(0);
  const gridRef = useRef(null);
  const virtualTableHiddenColumn = 'virtualTableHiddenColumn';
  const [mergedColumns, setMergedColumns] = useState<any[]>([]);
  const [finalColumns, setFinalColumns] = useState<any[]>([]);
  const refreshVirtualTable = ()=>{
    if (gridRef?.current) {
      (gridRef.current as any)?.resetAfterRowIndex(0);
      (gridRef.current as any)?.resetAfterColumnIndex(0);
    }
  }

  const getTotalColumnWidth = ()=>{
    let width = 0;
    columns.forEach((value: any) => {
      width = width + (value?.width || 0);
    })
    return width;
  }

  useLayoutEffect(() => {
    const totalWidth = getTotalColumnWidth();
    const allColumns = [...columns, {
      title: '',
      dataIndex: virtualTableHiddenColumn,
      disableFilterColumnOptions: true,
      disableDrag: true,
      width: (totalWidth - tableWidth) > 0 ? totalWidth - tableWidth : 0,
      render: () => {
        return (<></>);
      }
    }]
    setMergedColumns(allColumns.map((column: any) => {
      return column;
    }));
    setFinalColumns(allColumns);
    refreshVirtualTable();
  }, [columns])

  useEffect(() => {
    refreshVirtualTable();
  }, [rowHeight])

  const renderCell = (columnIndex: number, rowIndex: number, rawData: any[][]) => {
    return mergedColumns[columnIndex].render(
      rawData[rowIndex][mergedColumns[columnIndex].dataIndex],
      rawData[rowIndex], rowIndex
    )
  }

  const calculateTableHeight = (rowLengths: number): number => {
    if (rowLengths * (rowHeight || normalRowHeight) > scroll.y) {
      return scroll.y;
    } else {
      let columnTotalWidth = 0;
      mergedColumns.forEach((element: any) => {
        columnTotalWidth += element.width;
      })
      return rowLengths * (rowHeight || normalRowHeight) + (columnTotalWidth > tableWidth ? 18 : 0)
    }
  }

  const getTotalWidthExcludeHiddenColumns = () => {
    let width = 0;
    mergedColumns.forEach((value) => {
      if (value?.dataIndex !== virtualTableHiddenColumn) {
        width = width + (value?.width || 0);
      }
    })
    return width;
  }

  const getColumnWidth = (totalHeight: number, index: number, width: number) => {
    if (totalHeight > scroll.y && index === mergedColumns.length - 1) {
      const lastColumnWidth = tableWidth - getTotalWidthExcludeHiddenColumns() - 15;
      return lastColumnWidth < 0 ? 0 : lastColumnWidth;
    } else if (index === mergedColumns.length - 1) {
      const lastColumnWidth = tableWidth - getTotalWidthExcludeHiddenColumns();
      return lastColumnWidth < 0 ? 0 : lastColumnWidth;
    } else {
      return width
    }
  }

  const renderVirtualList = (rawData: any[][], {onScroll}: any) => {
    const totalHeight = rawData.length * (rowHeight || normalRowHeight);
    return (
      <Grid ref={gridRef}
            className="virtual-grid"
            columnCount={mergedColumns.length}
            columnWidth={(index) => {
              const {width} = mergedColumns[index];
              return getColumnWidth(totalHeight, index, width);
            }}
            height={calculateTableHeight(rawData.length)}
            rowCount={rawData.length}
            rowHeight={() => rowHeight || normalRowHeight}
            width={tableWidth}
            onScroll={({scrollLeft}) => {
              onScroll({
                scrollLeft,
              });
            }}
      >
        {({columnIndex, rowIndex, style}) => (
          <div
            className={classNames({
                'zebra-odd': rowIndex % 2 !== 0
              }, 'virtual-table-cell', {
                'virtual-table-cell-last':
                  columnIndex === mergedColumns.length - 1,
              }
            )}
            style={style}
          >
            {
              renderCell(columnIndex, rowIndex, rawData)
            }
          </div>
        )}
      </Grid>
    );
  };

  const onColumnChange = (column: any[]) => {
    setMergedColumns(column);
    refreshVirtualTable();
  }


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

export default VirtualTable;

参考

Ant Design + react-drag-listview实现Table拖拽变换列位置

Ant Design + react-resizable实现列表页可拖拽宽度变化

使用react-window构造虚拟列表(性能优化)

mini react-window(二) 实现可知变化高度虚拟列表

长列表优化:用 React 实现虚拟列表

浅说虚拟列表的实现原理

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
使用 antd table 和 react-window 实现虚拟列表时,固定可以通过以下步骤实现: 1. 首先,在 antd table 组件中设置固定,可以通过 `fixed` 属性来实现,例如: ```jsx <Table dataSource={data} columns={columns} scroll={{ x: '100vw' }} pagination={false} rowKey="id" sticky /> ``` 其中,`sticky` 属性用于开启固定功能。 2. 然后,在 react-window 中创建两个列表组件:一个用于显示固定,另一个用于显示非固定。我们可以使用 `FixedSizeList` 组件来实现固定列表使用 `VariableSizeList` 组件来实现非固定列表。例如: ```jsx // 固定列表 const FixedColumnList = ({ height, width, columnWidth, rowCount, rowHeight, columns }) => ( <FixedSizeList height={height} width={width} itemCount={rowCount} itemSize={rowHeight} itemData={{ columns }} > {Row({ isScrolling: false })} </FixedSizeList> ); // 非固定列表 const VariableColumnList = ({ height, width, columnWidth, rowCount, rowHeight, data, columns }) => ( <VariableSizeList height={height} width={width} itemCount={rowCount} itemSize={index => { // 根据行数据计算行高 const row = data[index]; return rowHeight(row); }} itemData={{ data, columns }} > {Row} </VariableSizeList> ); ``` 其中,`Row` 组件用于渲染每一行数据,其实现可以参考 antd table 组件中的 `rowRender` 方法。 3. 最后,将固定列表和非固定列表组合在一起,例如: ```jsx <FixedSizeList height={height} width={fixedWidth} itemCount={rowCount} itemSize={rowHeight} itemData={{ columns: fixedColumns, data }} > {Row({ isScrolling })} </FixedSizeList> <VariableSizeList height={height} width={variableWidth} itemCount={rowCount} itemSize={index => { // 根据行数据计算行高 const row = data[index]; return rowHeight(row); }} itemData={{ data, columns: variableColumns }} > {Row} </VariableSizeList> ``` 其中,`fixedWidth` 和 `variableWidth` 分别表示固定和非固定宽度,可以通过计算得出。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值