还在被超长列表卡到崩溃?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条以上?→ 果断上虚拟列表!

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

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

相关推荐
顾安r2 小时前
11.8 脚本网页 星际逃生
c语言·前端·javascript·flask
Hello.Reader2 小时前
Data Sink定义、参数与可落地示例
java·前端·网络
im_AMBER2 小时前
React 17
前端·javascript·笔记·学习·react.js·前端框架
一雨方知深秋2 小时前
2.fs模块对计算机硬盘进行读写操作(Promise进行封装)
javascript·node.js·promise·v8·cpython
谷歌开发者3 小时前
Web 开发指向标 | Chrome 开发者工具学习资源 (六)
前端·chrome·学习
一晌小贪欢3 小时前
【Html模板】电商运营可视化大屏模板 Excel存储 + 一键导出(已上线-可预览)
前端·数据分析·html·excel·数据看板·电商大屏·大屏看板
发现你走远了3 小时前
连接模拟器网页进行h5的调试(使用Chrome远程调试(推荐)) 保姆级图文
前端·chrome
街尾杂货店&4 小时前
css - 实现三角形 div 容器,用css画一个三角形(提供示例源码)简单粗暴几行代码搞定!
前端·css
顺凡4 小时前
删一个却少俩:Antd Tag 多节点同时消失的原因
前端·javascript·面试
小白路过4 小时前
CSS transform矩阵变换全面解析
前端·css·矩阵