写在前面:
在开发小程序时,长列表卡顿几乎是每个开发者的噩梦。
我原本以为靠最顶尖的 AI 助手,花点小钱就能买个"现成的轮子"轻松过关。
结果,我白白交了 10 多美金的"智商税",最后却发现,解决问题的钥匙其实就在自己手里。
1. 消失的 10 美金,和那个"带不动"的轮子
最近我在用 React 写一个小程序,遇到了那个经典难题:数据一多,列表就卡。
按照惯例,这事儿得用"虚拟列表(VirtualList)"来解。为了省事,我找了号称目前最强的 AI 助手 OpenClaw。
我对它说:"帮我搞个虚拟列表,要稳,要快。"
它的表现确实像个"专家",啪嗒啪嗒一通操作:引入第三方库、重构现有组件、优化逻辑......当时我心想:这几块钱花得值,专家出手就是不一样!
结果,现实给了我一记响亮的耳光。

这套代码在浏览器预览时看着还行,一搬到微信开发者工具里,直接原地"罢工":
- 布局莫名其妙歪了;
- 滚动监听时灵时不灵;
- 甚至还报了一些奇奇怪怪的兼容性错误。
我当时还没死心,觉得可能就是点小 Bug,接着跟 AI 聊,指望它能帮我修好。整整一个上午,我就在那儿不停地改、不停地试。
一看账单,10 多美金的 API 费用烧进去了,问题依然纹丝不动。 这种成本在涨、进度是零的感觉,真的让人想摔键盘。
2. 算了吧,我自己来!
最后我实在没辙了,一咬牙:既然别人的轮子我带不动,那我干脆自己手搓一个吧!
以前我总觉得虚拟列表这种"高端货"肯定很复杂,得考虑各种边界情况。结果当我真正静下心来琢磨原理时,我愣住了。
原来这玩意儿,竟然简单到离谱!
剥开那些花里胡哨的外壳,虚拟列表的逻辑其实只有三步:
- 只画看得见的:用户屏幕就那么大,除了这十几条数据,其他的都不渲染。
- 算好看不见的:在屏幕外面,上下各放一个空白的 View,把高度算准了,用来占位,假装数据还在。
- 滚一下算一下:随着用户滚动,实时算一下现在该显示哪几行,顺便把上下两个占位的高度调一下。
你看,这就是我最后撸出来的核心逻辑,清清爽爽,一眼就能看透:
jsx
// 核心逻辑:就算你有 10000 条数据,我给 React 渲染的永远只有十几条
const { topSpacerHeight, bottomSpacerHeight, visibleItems, startIndex } = useMemo(() => {
// 1. 计算可视区域能放几行
const viewportHeight = Math.max(viewportH || 600, rowHeight * 3);
const visibleCount = Math.ceil(viewportHeight / rowHeight);
// 2. 稍微多画 5 行(Buffer),免得用户滑太快看到白屏
const buffer = 5;
// 3. 计算当前该显示哪一段数据
const start = Math.max(0, Math.floor(scrollTop / rowHeight) - buffer);
const end = Math.min(data.length, start + visibleCount + buffer * 2);
// 4. 算出上下两个"占位墙"的高度
const topHeight = start * rowHeight;
const bottomHeight = Math.max(0, (data.length - end) * rowHeight);
const items = data.slice(start, end);
return { topSpacerHeight: topHeight, bottomSpacerHeight: bottomHeight, visibleItems: items, startIndex: start };
}, [data, rowHeight, scrollTop, viewportH]);
在界面(JSX)上,也就这三块内容在撑场面:
jsx
<ScrollView scrollY onScroll={(e) => setScrollTop(e.detail.scrollTop)}>
{/* 上面撑开高度,假装上面有数据 */}
<View style={{ height: topSpacerHeight }} />
{/* 中间这一小撮真实内容 */}
{visibleItems.map((item, index) => (
<View key={itemKey(item)} style={{ height: rowHeight }}>
{renderItem(item, startIndex + index)}
</View>
))}
{/* 下面撑开高度,假装下面有数据 */}
<View style={{ height: bottomSpacerHeight }} />
</ScrollView>
3. 这一波,我赢麻了
代码撸出来跑通的那一刻,我心里的石头落地了。
不仅 Bug 没了,还完美适配了微信环境。没有乱七八糟的库,没有复杂的配置。
- 滑得贼溜:因为 DOM 节点极少,小程序跑起来一点都不喘。
- 心里有底:代码是我自己写的,每一行我都能看懂,以后想怎么改就怎么改。
- 真省钱啊:这次自研,满打满算耗的 AI 额度连 1 美金都不到(前面那 10 刀真是交了智商税)。

4. 哥们儿的一点感悟
这次折腾让我明白一个理儿:方法不对,努力白费。
咱们开发者平时可能太依赖那些现成的"神级库"或者是 AI 助手了。但回过头来看:
- 大道至简:很多牛逼的技术,剥开外壳其实就是最基础的数学计算。
- AI 是帮手,不是亲爹:它能帮你写代码,但它没法替你思考。尤其是小程序这种环境复杂的坑,它不一定填得平。
- 与其花钱让别人帮你绕迷宫,不如你自己站高点,直接看出口在哪儿。
这次不仅省了钱,最重要的是,我终于把虚拟列表这点事儿给彻底整明白了。
这种"掌控感",才是写代码最爽的地方。
如果你也遇到了小程序性能问题,或者也被 AI 坑过,欢迎在评论区聊聊你的故事!