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

Posted c.

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 实现虚拟列表

浅说虚拟列表的实现原理

以上是关于react-window构造的虚拟列表使用react-resizable动态调整宽度和使用react-drag-listview拖拽变换列位置的问题的主要内容,如果未能解决你的问题,请参考以下文章

记录React性能优化之“虚拟滚动”技术——react-window

使用 react-window 渲染表行时如何处理“警告:validateDOMNesting(...): <tr> 不能作为 <div> 的子项出现。”

将 React-Window 添加到 Material-UI 增强表:类型无效 - 预期为字符串或类/函数,但得到:数组

变量作用域构造方法。

reac-redux使用

reac-redux使用