Skip to content

虚拟列表

在现代 Web 开发中,我们经常会遇到需要展示大量数据(如聊天记录、用户列表或日志信息)的场景。然而,直接渲染所有数据不仅会带来性能上的瓶颈,还可能引发内存泄漏等问题。为了优化这类场景,虚拟列表(Virtual List) 成为了一种非常有效的解决方案。

本文将深入探讨虚拟列表的概念,并详细介绍如何实现 等高虚拟列表不等高虚拟列表

什么是虚拟列表?

虚拟列表是一种通过“延迟渲染”来优化性能的技术。它的核心思想是:只渲染当前可见的几项数据,而忽略那些不在视口中的内容。这种技术特别适用于需要处理大量数据但实际显示区域有限的场景(如长列表或滚动容器)。

为什么使用虚拟列表?

  • 提升性能:减少一次性渲染的数据量,降低 CPU 和 GPU 的负担。
  • 节省内存:避免同时存储和渲染所有数据项。
  • 优化用户体验:即使在处理大量数据时,页面也能保持流畅的滚动效果。

虚拟列表的常见类型

1. 等高虚拟列表(Fixed Height Virtual List)

等高虚拟列表是指每一行的高度完全相同。这种类型的虚拟列表实现相对简单,因为所有项的高度一致,可以轻松计算视口内的可见范围。

实现步骤:

  1. 确定容器高度和滚动区域:需要知道容器的总高度、每项的高度以及当前滚动位置。
  2. 分页渲染:根据当前滚动位置,动态计算需要渲染的数据范围,并将其他数据隐藏在 DOM 中或从未创建。
  3. 滚动事件监听:当用户滚动时,重新计算可见区域并更新渲染的数据。

示例代码(React):

jsx
function FixedHeightVirtualList({
  items,
  height = 40, // 每项高度
  containerHeight = 500, // 容器高度
}) {
  const [scrollTop, setScrollTop] = useState(0);
  const [startIndex, setStartIndex] = useState(0);

  const handleScroll = (e) => {
    setScrollTop(e.target.scrollTop);
    // 计算当前可见起始索引
    const startIndex = Math.floor(scrollTop / height);
    setStartIndex(startIndex);
  };

  return (
    <div style={{ height: containerHeight, overflow: 'auto' }} onScroll={handleScroll}>
      {items.slice(startIndex * height, (startIndex + 1) * height).map((item, index) => (
        <div key={index} style={{ height }}>
          {item}
        </div>
      ))}
    </div>
  );
}
function FixedHeightVirtualList({
  items,
  height = 40, // 每项高度
  containerHeight = 500, // 容器高度
}) {
  const [scrollTop, setScrollTop] = useState(0);
  const [startIndex, setStartIndex] = useState(0);

  const handleScroll = (e) => {
    setScrollTop(e.target.scrollTop);
    // 计算当前可见起始索引
    const startIndex = Math.floor(scrollTop / height);
    setStartIndex(startIndex);
  };

  return (
    <div style={{ height: containerHeight, overflow: 'auto' }} onScroll={handleScroll}>
      {items.slice(startIndex * height, (startIndex + 1) * height).map((item, index) => (
        <div key={index} style={{ height }}>
          {item}
        </div>
      ))}
    </div>
  );
}

2. 不等高虚拟列表(Variable Height Virtual List)

不等高虚拟列表是指每一行的高度可能不同。这种类型的虚拟列表实现较为复杂,因为需要动态计算每项的累积高度,并根据滚动位置确定哪些项需要渲染。

实现步骤:

  1. 预计算高度:预先为每个数据项计算其高度(可以是固定值或动态计算)。
  2. 累积高度数组:创建一个累积高度数组,用于快速查找某个索引对应的位置。
  3. 滚动事件监听:当用户滚动时,通过二分法查找当前可见区域的起始和结束索引,并渲染对应的数据项。

示例代码(React):

jsx
function VariableHeightVirtualList({
  items,
}) {
  const [scrollTop, setScrollTop] = useState(0);
  // 预计算每项的高度并生成累积高度数组
  const heights = items.map((item) => /* 根据需求计算高度 */ 40);
  const cumulativeHeights = heights.reduce((acc, curr) => [...acc, acc[acc.length - 1] + curr], [0]);

  const handleScroll = (e) => {
    setScrollTop(e.target.scrollTop);
    // 使用二分法查找当前可见的起始和结束索引
    const startIndex = findStartIndex(cumulativeHeights, scrollTop);
    const endIndex = findEndIndex(cumulativeHeights, scrollTop + window.innerHeight);
  };

  return (
    <div style={{ overflow: 'auto' }} onScroll={handleScroll}>
      {/* 根据startIndex和endIndex渲染数据 */}
    </div>
  );
}

// 辅助函数:查找起始索引
function findStartIndex(cumulative, target) {
  let low = 0;
  let high = cumulative.length - 1;
  while (low < high) {
    const mid = Math.floor((low + high) / 2);
    if (cumulative[mid] <= target) {
      low = mid + 1;
    } else {
      high = mid;
    }
  }
  return low - 1;
}
function VariableHeightVirtualList({
  items,
}) {
  const [scrollTop, setScrollTop] = useState(0);
  // 预计算每项的高度并生成累积高度数组
  const heights = items.map((item) => /* 根据需求计算高度 */ 40);
  const cumulativeHeights = heights.reduce((acc, curr) => [...acc, acc[acc.length - 1] + curr], [0]);

  const handleScroll = (e) => {
    setScrollTop(e.target.scrollTop);
    // 使用二分法查找当前可见的起始和结束索引
    const startIndex = findStartIndex(cumulativeHeights, scrollTop);
    const endIndex = findEndIndex(cumulativeHeights, scrollTop + window.innerHeight);
  };

  return (
    <div style={{ overflow: 'auto' }} onScroll={handleScroll}>
      {/* 根据startIndex和endIndex渲染数据 */}
    </div>
  );
}

// 辅助函数:查找起始索引
function findStartIndex(cumulative, target) {
  let low = 0;
  let high = cumulative.length - 1;
  while (low < high) {
    const mid = Math.floor((low + high) / 2);
    if (cumulative[mid] <= target) {
      low = mid + 1;
    } else {
      high = mid;
    }
  }
  return low - 1;
}

虚拟列表的性能优化

无论哪种类型的虚拟列表,以下几点都可以显著提升性能:

  • 分页渲染:只渲染当前可见的数据项。
  • 动态调整缓存:保留少量上层和下层数据作为缓存,减少滚动时的重渲次数。
  • 使用 CSS 优化:通过 visibility: hiddendisplay: none 隐藏未渲染的内容。

更高级的虚拟列表实现

为了进一步提升性能和可维护性,可以考虑以下更高级的实现方式:

  1. 委托库:使用 React 的 useVirtualList 库或其他第三方库。
  2. 手动优化:通过计算和缓存技术减少 DOM 操作。

总结

虚拟列表是一种非常有用的性能优化技术,尤其适用于需要处理大量数据的场景。无论是等高还是不等高的实现方式,都可以显著提升页面的滚动流畅度和整体性能。选择哪种类型取决于具体需求:如果数据项的高度一致,则使用 等高虚拟列表 更为简单;如果高度不固定,则需要实现 不等高虚拟列表

通过合理的实现和优化,你可以轻松构建高效且响应迅速的虚拟列表组件!

Released under the MIT License.