还在被超长列表卡到崩溃?3招搞定虚拟滚动,性能直接起飞!

你是不是也遇到过这种场景?产品经理兴冲冲跑过来说:"这个页面要展示1万条数据,用户体验一定要流畅哦!"结果你吭哧吭哧写完,页面一加载......直接卡成PPT,滚动起来比看幻灯片还刺激。

别慌!今天我就带你彻底搞懂虚拟列表这个神器,让你轻松驾驭万级数据渲染,从此告别卡顿烦恼!

什么是虚拟列表?为什么能这么牛?

简单来说,虚拟列表就是个"聪明偷懒"的渲染方案。它不会傻乎乎地把所有数据都塞进DOM里,而是只渲染你看得见的那部分内容。

想象一下,你有个能装1000个item的列表,但屏幕一次只能显示10个。传统做法是把1000个都渲染出来,而虚拟列表只会渲染当前看得见的10个,外加上下各预留几个做缓冲。当你滚动时,它就像个魔术师一样快速切换显示的内容。

这样做的好处简直不要太明显:DOM节点数量暴降,内存占用大幅减少,滚动流畅得飞起!实测下来,万级数据列表也能做到60fps丝滑滚动。

手把手教你用现成轮子(react-window)

自己造轮子太累?咱们先来看看业界神器react-window怎么用。三行代码就能搞定基础需求,简单到哭!

jsx 复制代码
import { FixedSizeList as List } from 'react-window';

// 最简单的虚拟列表实现
const MyVirtualList = () => (
  <List
    height={400} // 列表容器高度
    itemCount={10000} // 总条目数
    itemSize={50} // 每个条目高度(固定高度时用)
    width={300} // 列表宽度
  >
    {({ index, style }) => (
      <div style={style}>
        我是第 {index} 条数据
      </div>
    )}
  </List>
);

看明白了吗?FixedSizeList适合所有item高度固定的场景。那个style参数特别重要,它会给每个item注入绝对定位的样式,让它们乖乖呆在正确的位置上。

如果你的item高度不固定,别慌,用VariableSizeList就行:

jsx 复制代码
import { VariableSizeList as List } from 'react-window';

const rowHeights = new Array(1000)
  .fill(true)
  .map(() => 25 + Math.round(Math.random() * 50));

const getItemSize = index => rowHeights[index];

const MyVariableList = () => (
  <List
    height={400}
    itemCount={1000}
    itemSize={getItemSize} // 传入高度计算函数
    width={300}
  >
    {({ index, style }) => (
      <div style={style}>
        我是高度不固定的第 {index} 条
      </div>
    )}
  </List>
);

自己动手实现?这些坑我帮你踩过了

虽然用库很方便,但了解原理很重要。自己实现一个基础版的虚拟列表,能帮你彻底搞懂它的工作原理。

jsx 复制代码
import React, { useState, useRef, useMemo } from 'react';

const MyOwnVirtualList = ({ items, itemHeight, containerHeight }) => {
  const [scrollTop, setScrollTop] = useState(0);
  const containerRef = useRef(null);

  // 计算可见区域的内容
  const visibleItems = useMemo(() => {
    const startIndex = Math.floor(scrollTop / itemHeight);
    const endIndex = Math.min(
      items.length - 1,
      startIndex + Math.ceil(containerHeight / itemHeight)
    );
    
    return items.slice(startIndex, endIndex + 1);
  }, [scrollTop, items, itemHeight, containerHeight]);

  // 计算内层容器的总高度(撑开滚动条)
  const innerContainerHeight = items.length * itemHeight;

  const handleScroll = (e) => {
    setScrollTop(e.currentTarget.scrollTop);
  };

  return (
    <div
      ref={containerRef}
      style={{
        height: containerHeight,
        overflow: 'auto',
        border: '1px solid #ddd'
      }}
      onScroll={handleScroll}
    >
      {/* 这个div用来撑开滚动条 */}
      <div style={{ height: innerContainerHeight }}>
        {/* 可见区域的内容 */}
        <div
          style={{
            position: 'relative',
            height: `${visibleItems.length * itemHeight}px`,
            top: `${Math.floor(scrollTop / itemHeight) * itemHeight}px`
          }}
        >
          {visibleItems.map((item, index) => (
            <div
              key={index}
              style={{
                position: 'absolute',
                top: `${index * itemHeight}px`,
                height: `${itemHeight}px`,
                width: '100%'
              }}
            >
              {item.content}
            </div>
          ))}
        </div>
      </div>
    </div>
  );
};

// 使用示例
const App = () => {
  const mockData = Array.from({ length: 10000 }, (_, i) => ({
    id: i,
    content: `列表项 ${i}`
  }));

  return (
    <MyOwnVirtualList
      items={mockData}
      itemHeight={50}
      containerHeight={400}
    />
  );
};

这个自实现版本虽然简单,但已经包含了虚拟列表的核心思想:计算可见区域、动态渲染、用绝对定位控制位置。

自己实现时最容易踩的坑:

  • 忘了给容器设置overflow: auto,结果根本滚不动
  • 滚动时出现空白,因为没及时更新可见区域的计算
  • 滚动条抖动,因为高度计算有误差
  • 快速滚动时内容闪烁,因为渲染速度跟不上滚动速度

实战技巧:让虚拟列表更强大

光会基础用法还不够,这些实战技巧能让你的虚拟列表更专业:

  1. 滚动节流:避免滚动时过于频繁地更新渲染
jsx 复制代码
// 用lodash的throttle或者自己实现
const throttledScroll = useMemo(
  () => throttle(handleScroll, 16), // 约60fps
  []
);
  1. 预渲染几屏内容:避免快速滚动时出现白屏
jsx 复制代码
// 在计算可见区域时,上下多算一些
const buffer = 5; // 多渲染5条
const startIndex = Math.max(0, Math.floor(scrollTop / itemHeight) - buffer);
const endIndex = Math.min(
  items.length - 1,
  startIndex + Math.ceil(containerHeight / itemHeight) + buffer
);
  1. 记住滚动位置:页面切换回来时保持之前的位置
jsx 复制代码
// 用useRef记住位置,或者存到localStorage
const savedScrollTop = useRef(0);

const handleScroll = (e) => {
  savedScrollTop.current = e.currentTarget.scrollTop;
  // ...其他逻辑
};

总结:什么时候该用虚拟列表?

虚拟列表虽好,但也不是万能药。我总结了个简单决策流程:

数据量小于100条?→ 直接正常渲染,别折腾 数据量100-1000条?→ 可以考虑分页加载 数据量1000条以上?→ 果断上虚拟列表!

记住,虚拟列表最适合的是那种"长得没边"的列表。如果是复杂表格或者需要频繁操作的数据,可能需要考虑其他方案。

如果觉得这篇文章有帮助,记得点赞收藏,下次遇到性能问题就不慌啦~

相关推荐
崔庆才丨静觅6 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60617 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了7 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅7 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅8 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅8 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment8 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅8 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊8 小时前
jwt介绍
前端
爱敲代码的小鱼8 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax