动态高度长列表滚动时高度计算不准怎么办?

熙然 Dev 阅读 11

我用React写了一个带不同行数卡片的长列表,用了虚拟滚动后,当展开/折叠卡片时,滚动条总跳来跳去,卡顿还计算不准总高度。

尝试用ref手动测量每个卡片高度,把总高度存在state里,但更新时列表会突然跳动。代码大概是这样:


const [totalHeight, setTotalHeight] = useState(0);
useEffect(() => {
  const heights = items.map(item => 
    ReactDOM.findDOMNode(item.ref).offsetHeight
  );
  setTotalHeight(heights.reduce((a,b)=>a+b,0));
}, [items]);

这样每次数据变化都重新计算,性能反而更差了。有没有更好的动态高度计算方案?

我来解答 赞 4 收藏
二维码
手机扫码查看
2 条解答
燕燕 Dev
动态高度的虚拟滚动确实挺烦人的,尤其是React里状态更新容易导致重绘抖动。我建议用 react-virtualized 或者 react-window 这类库来处理,它们对动态高度有专门的支持。

代码给你:

import React, { useState, useRef } from 'react';
import { VariableSizeList as List } from 'react-window';

function DynamicHeightList({ items }) {
const listRef = useRef(null);

// 初始化每行的高度缓存
const [rowHeights, setRowHeights] = useState(() =>
new Array(items.length).fill(50) // 默认值设为50
);

// 更新某一行的高度
const updateRowHeight = (index) => {
if (listRef.current) {
const node = document.getElementById(row-${index});
if (node) {
const height = node.getBoundingClientRect().height;
setRowHeights((prev) => {
const newRowHeights = [...prev];
newRowHeights[index] = height;
return newRowHeights;
});
listRef.current.resetAfterIndex(index);
}
}
};

// 渲染每一行的内容
const Row = ({ index, style }) => {
const item = items[index];
return (
row-${index}} style={style} onLoad={() => updateRowHeight(index)}>
{item.content}

);
};

return (
ref={listRef}
height={400} // 视口高度
width={300} // 视口宽度
itemCount={items.length}
itemSize={(index) => rowHeights[index] || 50} // 动态高度
>
{Row}

);
}


关键点:
1. 用 react-windowVariableSizeList 来支持动态高度。
2. 每一行渲染时通过 onLoad 或其他生命周期(比如点击展开/折叠)触发高度测量。
3. 测量后更新对应索引的高度缓存,并调用 resetAfterIndex 方法通知列表重新计算布局。
4. 默认高度先给一个预估值(比如50),避免初次渲染时抖动。

注意:如果展开/折叠卡片是异步操作(比如动画),可以在动画结束后再调用 updateRowHeight,否则高度可能测量不准。

这个方案性能不错,因为只会在需要时重新计算受影响的部分,而不是整个列表。写完记得测一下极端情况,比如快速连续展开多个卡片,看看会不会有延迟或者错位。
点赞
2026-02-20 09:08
翌菡 Dev
你这个方法确实太暴力了,每次都重新计算所有卡片高度,性能肯定崩。建议换个思路,用增量更新的方式,只计算发生变化的部分。

代码放这了:

const [heights, setHeights] = useState({});

useEffect(() => {
const newHeights = {};
items.forEach(item => {
if (item.ref.current) {
newHeights[item.id] = item.ref.current.offsetHeight;
}
});
setHeights(prev => ({ ...prev, ...newHeights }));
}, [items]);

const totalHeight = Object.values(heights).reduce((a, b) => a + b, 0);


重点是把每个item的高度缓存起来,只有当某个item实际变化时才更新它的高度。这样能大幅减少不必要的计算。

另外推荐你用react-window或react-virtualized这类库,它们专门处理动态高度的场景。比如react-window的VariableSizeList组件就很适合你这个需求,它内部已经帮你处理好高度测量和缓存了。

实在不行就直接上现成的库,别自己折腾了,这种轮子没必要重复造。我之前也踩过这个坑,最后还是乖乖用了现成方案,省心多了。
点赞
2026-02-19 10:06