You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在React Virtualized的可展开面板中虚拟化内部行?

解决React Virtualized可展开面板内子列表的虚拟化优化问题

针对外层虚拟化列表中,可展开面板一次性加载500+子行导致UI卡顿的问题,核心是让外层列表正确感知展开行的总高度,同时对子列表做虚拟化处理,以下是具体实现方案:

关键思路

外层List需要动态计算每行的高度(区分折叠/展开状态),展开时要包含子列表的总高度;子列表用List组件实现虚拟化,且禁用自身滚动,完全由外层列表控制滚动逻辑,确保仅渲染可见区域的子行。

具体实现步骤

1. 引入CellMeasurer缓存父项高度

使用CellMeasurer和CellMeasurerCache缓存折叠状态下父项的高度,避免重复计算:

import { List, CellMeasurer, CellMeasurerCache } from 'react-virtualized';

// 初始化缓存,defaultHeight为父项折叠时的高度
const cache = new CellMeasurerCache({
  defaultHeight: 50,
  fixedWidth: true
});

2. 动态计算外层List的行高

外层List的rowHeight改为函数,根据行的展开状态返回对应高度:

// 子行固定高度(根据实际业务调整)
const CHILD_ROW_HEIGHT = 30;

const getRowHeight = ({ index }) => {
  const item = listItems[index];
  if (item.isExpanded) {
    // 展开时高度 = 父项高度 + 子列表总高度(子行数×子行高度)
    return cache.getHeight(index) + item.children.length * CHILD_ROW_HEIGHT;
  }
  // 折叠时仅返回父项高度
  return cache.getHeight(index);
};

3. 实现父项渲染逻辑(包含虚拟化子列表)

在父项的rowRenderer中,展开时嵌套子List,并禁用其自身滚动:

const renderParentRow = ({ index, key, style }) => {
  const item = listItems[index];

  const toggleExpand = () => {
    // 更新列表项的展开状态
    const updatedItems = [...listItems];
    updatedItems[index].isExpanded = !updatedItems[index].isExpanded;
    setListItems(updatedItems);
  };

  return (
    <CellMeasurer cache={cache} columnIndex={0} key={key} rowIndex={index}>
      <div style={style}>
        {/* 展开/折叠切换按钮 */}
        <div onClick={toggleExpand} style={{ padding: '10px', cursor: 'pointer' }}>
          {item.isExpanded ? '▼' : '▶'} {item.title}
        </div>

        {/* 虚拟化子列表 */}
        {item.isExpanded && (
          <List
            width={style.width} // 继承父项宽度
            height={item.children.length * CHILD_ROW_HEIGHT} // 子列表总高度
            rowCount={item.children.length}
            rowHeight={CHILD_ROW_HEIGHT}
            rowRenderer={({ index: childIndex, style: childStyle }) => (
              <div style={{ ...childStyle, padding: '8px' }}>
                {item.children[childIndex]}
              </div>
            )}
            scrollEnabled={false} // 禁用子列表滚动,由外层控制
          />
        )}
      </div>
    </CellMeasurer>
  );
};

4. 外层List的最终配置

将动态行高和父项渲染函数传入外层List:

<List
  width={window.innerWidth}
  height={window.innerHeight}
  rowCount={listItems.length}
  rowHeight={getRowHeight}
  rowRenderer={renderParentRow}
/>

核心优化点说明

  • 高度计算准确性:通过预计算子列表总高度,让外层List能正确计算滚动范围,避免滚动时出现内容跳跃或渲染异常。
  • 子列表虚拟化:子List仅渲染当前可见区域的子行,即使有500+条数据,也只会渲染屏幕内的行数,大幅减少DOM节点数量。
  • 缓存机制:CellMeasurerCache缓存父项高度,避免每次渲染都重新测量,提升性能。

内容的提问来源于stack exchange,提问作者Aman Sadhwani

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 01:02:45