React 长列表虚拟滚动实战:手写可视区渲染,10 万条数据不卡顿
React 长列表虚拟滚动实战:手写可视区渲染,10 万条数据不卡顿后台管理系统里经常遇到这种需求:一次性拉回来几万条日志、几万个 SKU,产品说不想分页,就要滚动加载全部。你老老实实list.map()渲染出来,浏览器直接卡死——10 万个 DOM 节点,首屏白屏两秒,滚动时掉帧到个位数。问题的根源很简单:用户视口里只能看到十几行,但你把 10 万个节点全塞进了 DOM。虚拟滚动(virtual scrolling)的思路就是——只渲染可视区内的那几十条,滚动时动态替换。这篇我们不套第三方库,手写一个定高虚拟列表,把原理彻底讲透。朴素写法:先看看它怎么卡的function PlainList({ items }) { return ( div style{{ height: 600, overflow: auto }} {items.map((item) ( div key{item.id} style{{ height: 40 }} {item.text} /div ))} /div ); } // items 有 100000 条时,React 要 diff 十万个 fiber 节点, // 首次挂载卡顿,之后每次滚动触发的重排也拖垮帧率。打开 DevTools 的 Performance 面板录一段,你会看到 Scripting 和 Rendering 两条长得吓人。DOM 节点数直接爆到十万级。核心思路:算出该渲染哪几条定高虚拟列表的数学非常直白。假设每行高度固定itemHeight,容器高度containerHeight,当前滚动距离scrollTop:总高度 items.length * itemHeight,用一个撑高的占位 div 保证滚动条长度正确。可视区第一条的索引 Math.floor(scrollTop / itemHeight)。可视区能放下的行数 Math.ceil(containerHeight / itemHeight)。把这一段切出来,用transform: translateY推到正确的位置。关键点:不要用margin-top或改变元素高度来定位,用transform——transform 不触发重排(reflow),只走合成层,滚动才跟手。正确写法:一个可用的 VirtualListimport { useState, useRef, useMemo } from react; function VirtualList({ items, itemHeight 40, containerHeight 600 }) { const [scrollTop, setScrollTop] useState(0); const containerRef useRef(null); // 多渲染几条做缓冲,避免快速滚动时上下边缘露白 const overscan 3; const { startIndex, endIndex, offsetY } useMemo(() { const visibleCount Math.ceil(containerHeight / itemHeight); const start Math.floor(scrollTop / itemHeight); // 上下各多切 overscan 条,并夹住边界 const startWithBuffer Math.max(0, start - overscan); const endWithBuffer Math.min( items.length, start visibleCount overscan ); return { startIndex: startWithBuffer, endIndex: endWithBuffer, // 偏移量必须按实际渲染的第一条算,否则内容会错位 offsetY: startWithBuffer * itemHeight, }; }, [scrollTop, itemHeight, containerHeight, items.length]); const visibleItems items.slice(startIndex, endIndex); const totalHeight items.length * itemHeight; return ( div ref{containerRef} style{{ height: containerHeight, overflow: auto, position: relative }} onScroll{(e) setScrollTop(e.currentTarget.scrollTop)} {/* 占位层:撑出真实滚动高度,让滚动条比例正确 */} div style{{ height: totalHeight }} {/* 内容层:用 transform 推到可视区,不触发重排 */} div style{{ transform: translateY(${offsetY}px) }} {visibleItems.map((item) ( div key{item.id} style{{ height: itemHeight }} {item.text} /div ))} /div /div /div ); }用 10 万条数据测:DOM 节点常驻只有二十几个(可视 缓冲),首屏瞬开,滚动稳定 60fps。const items Array.from({ length: 100000 }, (_, i) ({ id: i, text: 第 ${i} 行数据, })); VirtualList items{items} /;坑一:offsetY 一定要按缓冲后的起点算新手最容易写错的一行:offsetY start * itemHeight,用的是没加 overscan 的start。结果就是往上滚动时内容整体错位overscan * itemHeight像素,列表抖一下。记住——你 slice 从哪条开始,translateY 就得推到那条的位置,两者必须用同一个索引。坑二:onScroll 高频触发要不要节流onScroll每帧可能触发多次,直觉上想throttle。但这里恰恰不要节流:setScrollTop触发的重渲染已经被 React 批处理,而且我们只改了少量 DOM。真正该做的是把滚动状态更新交给浏览器的渲染节奏——如果你发现掉帧,用requestAnimationFrame包一层,而不是用定时器节流(定时器会让内容滞后于滚动条,露白更明显)。const rafId useRef(null); const handleScroll (e) { const top e.currentTarget.scrollTop; if (rafId.current) return; // 一帧内只更新一次 rafId.current requestAnimationFrame(() { setScrollTop(top); rafId.current null; }); };坑三:不定高怎么办上面的方案要求每行等高。真实业务里很多是不定高(比如带图片、可折叠的评论)。定高方案直接失效,因为你算不出scrollTop对应第几条。这时候的通用做法是动态测量 位置缓存:先用估算高度渲染,挂载后用ResizeObserver量出真实高度,存进一个Mapindex, height,再据此累加算偏移。工程量陡增,这也是为什么生产环境不定高场景直接上tanstack/react-virtual或react-window——它们把测量、缓存、滚动锚定都处理好了。定高场景手写完全够用,不定高才值得引库。小结长列表卡顿的本质是DOM 节点太多,虚拟滚动只渲染可视区 缓冲的几十条。定高列表三件套:撑高的占位层(保证滚动条比例)、translateY 定位的内容层(不触发重排)、按滚动距离算出的切片区间。offsetY必须和slice用同一个起点索引,否则内容错位。滚动更新别用定时器节流,要么直接 setState 让 React 批处理,要么用requestAnimationFrame对齐渲染帧。一句话记忆:用户只看得到一屏,你就只渲染一屏——剩下的用高度撑着、用 transform 骗过去。

相关新闻

Unity粒子系统实现Logo动态特效:从路径点收敛到性能优化全解析

Unity粒子系统实现Logo动态特效:从路径点收敛到性能优化全解析

1. 项目概述:为什么动态Logo特效是品牌视觉的“点睛之笔”在数字内容创作,尤其是游戏、应用启动界面、品牌展示视频中,一个静态的Logo往往显得过于“安静”。当用户点击启动按钮,或者进入一个关键场景时,一个能够“呼吸…

2026/8/10 12:15:24 阅读更多 →
Git命令行可视化管理工具:lazygit

Git命令行可视化管理工具:lazygit

小伙伴们,大家好,我是小溪,见字如面。最近一直在使用CLI开发,已经很久没有打开过IDE了,只有在检查Diff和提交代码时才会打开IDE,那有没有可以不依赖IDE就能可视化查看Diff和管理Git的工具呢?还真…

2026/8/10 12:14:24 阅读更多 →
Nintendo Switch文件管理的终极工具:NSC_BUILDER完全指南 [特殊字符]

Nintendo Switch文件管理的终极工具:NSC_BUILDER完全指南 [特殊字符]

Nintendo Switch文件管理的终极工具:NSC_BUILDER完全指南 🎮 【免费下载链接】NSC_BUILDER Nintendo Switch Cleaner and Builder. A batchfile, python and html script based in hacbuild and Nuts python libraries. Designed initially to erase tit…

2026/8/10 12:14:24 阅读更多 →

最新新闻

NX-HBMenu深度故障排除:10个技术难题的实践解决方案

NX-HBMenu深度故障排除:10个技术难题的实践解决方案

NX-HBMenu深度故障排除:10个技术难题的实践解决方案 【免费下载链接】nx-hbmenu The Nintendo Switch Homebrew Menu 项目地址: https://gitcode.com/gh_mirrors/nx/nx-hbmenu NX-HBMenu作为Nintendo Switch自制系统的核心启动菜单,为Homebrew应用…

2026/8/10 13:01:41 阅读更多 →
5分钟搞定国家中小学智慧教育平台电子课本下载:免费PDF获取全攻略

5分钟搞定国家中小学智慧教育平台电子课本下载:免费PDF获取全攻略

5分钟搞定国家中小学智慧教育平台电子课本下载:免费PDF获取全攻略 【免费下载链接】tchMaterial-parser 国家中小学智慧教育平台 电子课本下载工具,帮助您从智慧教育平台中获取电子课本的 PDF 文件网址并进行下载,让您更方便地获取课本内容。…

2026/8/10 13:01:41 阅读更多 →
电梯远程调试方案:工业物联网与AR技术的成本优化实践

电梯远程调试方案:工业物联网与AR技术的成本优化实践

1. 项目背景与核心价值 作为一名在电梯行业摸爬滚打十年的老工程师,我见过太多调试环节的成本黑洞。传统电梯调试往往需要厂家技术员跨省出差,光是差旅费就能吃掉项目利润的30%。去年我在一个老旧小区改造项目中,摸索出一套"零差旅"…

2026/8/10 13:01:41 阅读更多 →
蚂蚁集团Ling 3.0 Flash开源大模型:本地部署、API调用与性能实测指南

蚂蚁集团Ling 3.0 Flash开源大模型:本地部署、API调用与性能实测指南

这次我们来看一个来自蚂蚁集团的开源大模型项目——Ling 3.0 Flash。它不是又一个只停留在论文里的概念,而是一个可以直接部署、推理、甚至通过API调用的实用工具。对于开发者来说,最关心的永远是:它能不能在我的机器上跑起来?显存…

2026/8/10 13:01:41 阅读更多 →
终极指南:3分钟搞定Mac双系统Boot Camp驱动自动安装

终极指南:3分钟搞定Mac双系统Boot Camp驱动自动安装

终极指南:3分钟搞定Mac双系统Boot Camp驱动自动安装 【免费下载链接】brigadier Fetch and install Boot Camp ESDs with ease. 项目地址: https://gitcode.com/gh_mirrors/bri/brigadier 还在为Mac安装Windows系统时繁琐的驱动安装而烦恼吗?Brig…

2026/8/10 13:01:41 阅读更多 →
终极指南:RevokeMsgPatcher - 一键解决微信/QQ/TIM防撤回难题

终极指南:RevokeMsgPatcher - 一键解决微信/QQ/TIM防撤回难题

终极指南:RevokeMsgPatcher - 一键解决微信/QQ/TIM防撤回难题 【免费下载链接】RevokeMsgPatcher :trollface: A hex editor for WeChat/QQ/TIM - PC版微信/QQ/TIM防撤回补丁(我已经看到了,撤回也没用了) 项目地址: https://git…

2026/8/10 13:00:41 阅读更多 →

日新闻

GraphQL-CSS API全解析:useGqlCSS、GqlCSS组件与getStyles实用指南

GraphQL-CSS API全解析:useGqlCSS、GqlCSS组件与getStyles实用指南

GraphQL-CSS API全解析:useGqlCSS、GqlCSS组件与getStyles实用指南 【免费下载链接】graphql-css A blazing fast CSS-in-GQL™ library. 项目地址: https://gitcode.com/gh_mirrors/gr/graphql-css GraphQL-CSS是一个基于GraphQL的CSS-in-GQL™库&#xff0…

2026/8/10 0:00:02 阅读更多 →
告别语言障碍:KISS Translator 双语翻译插件终极指南

告别语言障碍:KISS Translator 双语翻译插件终极指南

告别语言障碍:KISS Translator 双语翻译插件终极指南 【免费下载链接】kiss-translator A simple, open source bilingual translation extension & Greasemonkey script (一个简约、开源的 双语对照翻译扩展 & 油猴脚本) 项目地址: https://gitcode.com/…

2026/8/10 0:00:02 阅读更多 →
BepInEx配置管理器:游戏插件配置的终极可视化解决方案

BepInEx配置管理器:游戏插件配置的终极可视化解决方案

BepInEx配置管理器:游戏插件配置的终极可视化解决方案 【免费下载链接】BepInEx.ConfigurationManager Plugin configuration manager for BepInEx 项目地址: https://gitcode.com/gh_mirrors/be/BepInEx.ConfigurationManager 你是否曾经因为游戏插件的复杂…

2026/8/10 0:00:02 阅读更多 →

周新闻

5分钟告别提取码焦虑:baidupankey如何智能破解百度网盘资源锁

5分钟告别提取码焦虑:baidupankey如何智能破解百度网盘资源锁

5分钟告别提取码焦虑:baidupankey如何智能破解百度网盘资源锁 【免费下载链接】baidupankey 在线查询网盘提取码(维护中 rm repo) 项目地址: https://gitcode.com/gh_mirrors/ba/baidupankey 你是否曾经在深夜寻找一份重要资料&#x…

2026/8/10 1:05:29 阅读更多 →
如何快速生成中国车牌图片:Python开源工具完整指南

如何快速生成中国车牌图片:Python开源工具完整指南

如何快速生成中国车牌图片:Python开源工具完整指南 【免费下载链接】chinese_license_plate_generator 中国车牌生成器 项目地址: https://gitcode.com/gh_mirrors/ch/chinese_license_plate_generator 中国车牌生成器是一个基于Python的开源项目&#xff0c…

2026/8/10 1:05:29 阅读更多 →
收藏!小白程序员轻松入门大模型,从Harness工程开始实践

收藏!小白程序员轻松入门大模型,从Harness工程开始实践

文章强调学习大模型不应只关注模型本身,而应重视模型外的系统搭建,即Harness。提出AgentModelHarness的实用公式,详细介绍Harness的四个层次:持久化层、执行层、控制层和观察与验证层。文章还探讨了上下文工程、工具设计、AGENTS.…

2026/8/10 1:05:29 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/9 17:05:02 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/10 1:05:29 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/9 17:05:02 阅读更多 →