假装渲染十万条,虚拟列表的障眼法你学会了吗?

你是否遇到过列表一多,页面就卡得像"老年机"?别怕,虚拟列表来拯救你!本文不仅带你搞懂为什么需要虚拟列表,还手把手教你如何用 React 打造高性能虚拟列表,代码、原理、细节全都有,轻松避坑!


一、为什么需要虚拟列表?

当你需要渲染成千上万条数据时,直接渲染所有 DOM 节点会让浏览器"罢工",页面卡顿、内存飙升、用户体验暴跌。

  • 性能瓶颈:DOM 多了,浏览器渲染压力大。
  • 内存消耗:每个节点都要占内存,数据越多越吃紧。
  • 用户体验差:滚动卡顿,交互延迟,分分钟劝退用户。

所以,虚拟列表就是前端的"省电模式",只渲染你能看到的那一小部分,其他的都假装"在场",让页面飞起来!🚀


二、虚拟列表的实现原理

虚拟列表的核心思想很简单:只渲染可视区域的内容,其他用占位元素撑起高度

1. 数据分片

  • slice 截取当前可见的数据片段。
  • 只渲染这部分,其他的都用一个"假容器"撑起来。

2. 占位容器

  • 用一个高度等于总数据长度的 div,让滚动条看起来像真的。
  • 真实渲染的列表通过 transform: translateY 精准定位到可视区。

3. 滚动监听

  • 监听滚动事件,计算当前滚动到第几个元素。
  • 动态更新可见数据的起止索引和偏移量。

4. 性能优化

  • 只更新必要的部分,避免无意义的重渲染。
  • 利用 useRefuseState 精准控制状态。

三、我是如何打造虚拟列表的?

1. 数据准备

jsx 复制代码
const listData = new Array(1000).fill(0).map((item, index) => ({ id: index, value: index }))

一千条数据,够你滑到天荒地老!

2. 计算可视区

jsx 复制代码
const visibleCount = Math.ceil(500 / 50) // 可视区最多显示多少条

容器高度 500px,每条 50px,最多显示 10 条。

3. 占位容器撑高度

jsx 复制代码
<div className='list-phantom' style={{height: listHeight}}></div>

撑起滚动条的"假身",让你以为真的有一千条。

4. 渲染可见数据

jsx 复制代码
const visibleData = listData.slice(startIndex, endIndex)

只渲染当前可见的那几条,其他的都在"后台待命"。

5. 滚动事件驱动

jsx 复制代码
const scrollEvent = () => {
  const scrollTop = listRef.current.scrollTop
  setStartIndex(Math.floor(scrollTop / 50))
  setStartOffset(scrollTop - (scrollTop % 50))
}

每次滚动都重新计算可见区,精准定位。

6. 视觉定位

jsx 复制代码
const getTransform = { transform: `translateY(${startOffset}px)` }

让真实渲染的列表"漂移"到正确的位置。

7. 样式美化

css 复制代码
.container { width: 400px; height: 500px; border: 1px solid #000; overflow-y: scroll; }
ul { list-style: none; margin: 0; padding: 0; }
li { height: 50px; border: 1px solid #000; text-align: center; line-height: 50px; }
.list-phantom { position: absolute; top: 0; left: 0; right: 0; }

让虚拟列表看起来"高大上",滑动顺畅。

8. 效果展示


四、完整代码展示与注释

jsx 复制代码
import React, { useEffect, useState, useRef } from 'react'
import './app.css'
// 构造一千条数据,实际场景可替换为十万条
const listData = new Array(1000).fill(0).map((item, index) => ({ id: index, value: index }))
export default function App() {
    // 占位容器的偏移距离(用于定位可见区)
    const [startOffset, setStartOffset] = useState(0)
    // 可视区起始索引
    const [startIndex, setStartIndex] = useState(0)
    // 可视区结束索引
    const [endIndex, setEndIndex] = useState(0)
    // 列表容器引用
    const listRef = useRef(null)
    // 每项高度,和容器高度(可根据实际调整)
    const itemHeight = 50
    const containerHeight = 500
    // 总列表高度
    const listHeight = listData.length * itemHeight
    // 可视区最多展示的项数
    const visibleCount = Math.ceil(containerHeight / itemHeight)
    // 每次起始索引变化,自动更新结束索引
    useEffect(() => {
        setEndIndex(startIndex + visibleCount)
    }, [startIndex])
    // 计算可视区偏移量,决定列表定位
    const getTransform = {
        transform: `translateY(${startOffset}px)`
    }
    // 截取当前可视区的数据
    const visibleData = listData.slice(startIndex, endIndex)
    // 滚动事件:动态计算起始索引和偏移量
    const scrollEvent = () => {
        const scrollTop = listRef.current.scrollTop // 容器已滚动的距离
        setStartIndex(Math.floor(scrollTop / itemHeight))   // 计算新的起始索引
        setStartOffset(scrollTop - (scrollTop % itemHeight)) // 计算新的偏移量
    }
    return (
        <div className='container' onScroll={scrollEvent} ref={listRef}>
            {/* 占位容器,撑起完整高度,保证滚动条正常 */}
            <div className='list-phantom' style={{height: listHeight}}></div>
            {/* 实际渲染的可视区列表,定位到正确位置 */}
            <div className='list' style={getTransform}>
                <ul>
                    {visibleData.map(item => (
                        <li key={item.id}>{item.value}</li>
                    ))}
                </ul>
            </div>
        </div>
    )
}

五、技术亮点与优化建议

  • 极致性能:只渲染可见区,页面再多数据都不卡。
  • 易扩展:支持任意高度、任意数据量,轻松适配各种场景。
  • 代码简洁:核心逻辑一目了然,维护无压力。
  • 可玩性强:可以加动画、懒加载、无限滚动等高级玩法。

虚拟列表就像前端的"魔术师",让你在有限的空间里装下无限的数据!🎩✨


六、应用场景

  • 聊天列表、评论区、商品列表、日志展示......只要数据多,都能用虚拟列表!
  • 移动端、PC端通吃,性能飞升,用户体验爆表。

七、原理总结(readme.md

markdown 复制代码
# 如何一次性渲染十万条数据
1. 根据当前可视区域的高度 计算截取的起始索引 (startIndex)
2. 根据当前可视区域的高度 计算截取的结束索引 (endIndex)
3. 截取数据数组(起始索引, 结束索引),获取当前可视区域的数据 (visibleData)
4. 渲染数据
5. 每次滚动出去一个列表项后, 因为展示的值会更新, 所以要将滚出去的列表向下移动回来 startOffset
6. 为了列表能正常滚动, 用一个空容器占位, 高度设为完整列表的高度

八、总结

虚拟列表是前端性能优化的"神器",只渲染你能看到的部分,其他的都用"障眼法"撑起来。本文不仅讲清了原理,还用代码和幽默语言带你实战打造,助你轻松掌握虚拟列表的精髓。下次遇到大数据量列表,再也不用怕页面卡顿啦!😎

相关推荐
Jenna的海糖几秒前
Vue 项目首屏加载速度优化
前端·javascript·vue.js
前端梭哈攻城狮6 分钟前
js计算精度溢出,自定义加减乘除类
前端·javascript·算法
北辰alk9 分钟前
React JSX 内联条件渲染完全指南:四招让你的UI动态又灵活
前端
前端小巷子11 分钟前
最长递增子序列:从经典算法到 Vue3 运行时核心优化
前端·vue.js·面试
zayyo12 分钟前
深入解读 SourceMap:如何实现代码反解与调试
前端
子兮曰13 分钟前
🚀 震惊!这20个现代JavaScript API,让90%的前端开发者直呼"相见恨晚"!
javascript·api
龙在天14 分钟前
以为 Hooks 是银弹,结果是新坑
前端
wayhome在哪24 分钟前
前端高频考题(css)
前端·css·面试
wayhome在哪33 分钟前
前端高频考题(html)
前端·面试·html
冰糖雪梨dd1 小时前
vue在函数内部调用onMounted
前端·javascript·vue.js