Next.js 虚拟列表封装:高效处理海量数据渲染

477 阅读3分钟

综述

虚拟列表是一种优化技术,专注于只渲染可视区域的数据,避免大量数据加载导致页面卡顿。

本文将通过一个简单易用的 useVirtualList Hook 来实现这个功能,并提供多个实际应用场景和示例代码,帮助开发者在 Next.js 中高效处理海量数据的渲染。


背景问题

在某个项目中,遇到以下问题:

  • 数据量大:接口返回了几万条数据,没有分页。
  • 页面表现:渲染列表时非常卡顿,点击交互也很慢。

为了解决这一问题,从分页加载方案转向虚拟列表。通过只渲染可视区域的数据,减少 DOM 元素的渲染,最终大幅提升页面性能。


什么是虚拟列表?

虚拟列表是一种优化渲染性能的技术,核心思想是:

  1. 只渲染可视区域的数据,其他数据用占位符模拟。
  2. 动态计算渲染内容,用户滚动时更新可视区域的内容。

应用场景:

  • 数据量较大的表格(如库存管理系统)。
  • 列表类组件(如聊天记录、文件目录)。
  • 选择器(如下拉菜单中有上千选项)。

核心实现

1. 滚动模拟

通过设置容器的 heightmax-height 实现滚动。利用虚拟高度模拟数据总量,从而触发滚动条。

2. 渲染正确的数据

根据滚动位置计算出当前需要渲染的数据索引范围,只渲染这些数据。

3. 渲染到可视区域

通过 transform: translateY 将数据偏移到可视区域,避免超出区域的元素被渲染。


定高虚拟列表实现

Hook 参数配置

以下是 useVirtualList 的配置参数:

  • data: 数据源(响应式)。
  • itemHeight: 每个列表项的固定高度。
  • size: 每次渲染的数据量。
  • scrollContainer: 滚动容器的选择器。
  • actualHeightContainer: 用于撑开高度的容器选择器。
  • translateContainer: 偏移容器的选择器。

Hook 核心代码

import { useState, useEffect, useMemo } from "react";

function useVirtualList(config) {
  const [startIndex, setStartIndex] = useState(0);
  const [endIndex, setEndIndex] = useState(config.size - 1);

  const actualRenderData = useMemo(() => {
    return config.data.slice(startIndex, endIndex + 1);
  }, [config.data, startIndex, endIndex]);

  useEffect(() => {
    const scrollContainer = document.querySelector(config.scrollContainer);
    const actualHeightContainer = document.querySelector(config.actualHeightContainer);
    const translateContainer = document.querySelector(config.translateContainer);

    actualHeightContainer.style.height = `${config.data.length * config.itemHeight}px`;

    const handleScroll = (e) => {
      const scrollTop = e.target.scrollTop;
      translateContainer.style.transform = `translateY(${scrollTop}px)`;

      setStartIndex(Math.floor(scrollTop / config.itemHeight));
      setEndIndex(startIndex + config.size - 1);
    };

    scrollContainer.addEventListener("scroll", handleScroll);

    return () => {
      scrollContainer.removeEventListener("scroll", handleScroll);
    };
  }, [startIndex, endIndex, config]);

  return { actualRenderData };
}

示例代码

1. 普通列表

<ul className="scroll-container">
  <div className="actual-height-container">
    <div className="translate-container">
      {actualRenderData.map((item) => (
        <li key={item.id}>
          {item.text}
        </li>
      ))}
    </div>
  </div>
</ul>

配置调用:

const { actualRenderData } = useVirtualList({
  data: tableData,
  itemHeight: 50,
  size: 20,
  scrollContainer: ".scroll-container",
  actualHeightContainer: ".actual-height-container",
  translateContainer: ".translate-container",
});

2. Next.js + Ant Design 表格

const { actualRenderData } = useVirtualList({
  data: tableData,
  itemHeight: 50,
  size: 10,
  scrollContainer: ".ant-table-body",
  actualHeightContainer: ".ant-table-content",
  translateContainer: ".ant-table-tbody",
});

不定高虚拟列表

对于列表项高度不固定的场景,需要动态计算每个列表项的实际高度并缓存。核心技术点:

  1. nextTick:等待 DOM 更新后计算实际高度。
  2. 高度缓存:通过缓存每个列表项的高度,避免重复计算。
  3. 滚动计算:根据滚动高度和缓存动态定位需要渲染的列表项。

总结

通过虚拟列表优化,页面能够轻松应对海量数据的渲染任务。无论是定高还是不定高场景,useVirtualList Hook 都能显著提升性能。

实际应用:

  • 在线电商后台快速浏览大量商品。
  • 聊天应用中流畅加载历史消息。
  • 数据分析系统中的报表渲染。

效果

虚拟列表能够处理 10w 条数据,保持丝滑滚动,为前端性能优化提供了一种简单而高效的解决方案。