资讯流、车型列表和搜索结果页都有一个共同特点:数据可以持续增加,但用户一次只能看到屏幕中的少量内容。如果页面随着数据增加不断渲染更多节点,滚动一段时间后就可能出现响应迟缓、内存增加和图片加载压力过大的问题。
这里需要优化的不是 React 中通常讨论的 Virtual DOM,而是虚拟列表,也叫窗口化渲染:数据仍然完整保存,但界面中只渲染当前可视范围附近的一段节点。
节点数量比数据数量更直接影响页面
假设一页加载 20 条资讯,用户连续翻过十页以后,页面可能保留 200 个卡片节点,每个卡片还包含图片、标题和标签。即使这些数据不再变化,平台仍然需要维护这批视图节点。
窗口化渲染将问题转换成另一种形式:
完整数据:200 条,仍然可以用于状态和跳转实际视图:当前屏幕附近约 10 至 20 条列表高度:使用占位空间保持滚动条位置正确
关键不是减少业务数据,而是减少同时进入渲染树的内容。
固定高度列表最容易开始
如果每个列表卡片高度一致,可以根据滚动距离直接计算当前需要展示的索引:
const ITEM_HEIGHT = 112;const BUFFER_COUNT = 4;function getVisibleRange(scrollTop, viewportHeight, total) { const firstVisible = Math.floor(scrollTop / ITEM_HEIGHT); const visibleCount = Math.ceil(viewportHeight / ITEM_HEIGHT); const start = Math.max(0, firstVisible - BUFFER_COUNT); const end = Math.min( total, firstVisible + visibleCount + BUFFER_COUNT ); return { start, end };}
缓冲区用于提前渲染视口上下方少量内容,避免用户快速滑动时看到空白。
模板只渲染切片后的数据,同时让列表容器保持完整高度:
function VirtualList({ items, scrollTop, viewportHeight }) { const { start, end } = getVisibleRange( scrollTop, viewportHeight, items.length ); const visibleItems = items.slice(start, end); return ( <View className="virtual-list" style={{ height: items.length * ITEM_HEIGHT }}> <View className="virtual-list__window" style={{ transform: `translateY(${start * ITEM_HEIGHT}px)` }} > {visibleItems.map(item => ( <ArticleCard key={item.id} article={item} /> ))} </View> </View> );}
小程序中实际可采用的样式写法和滚动组件能力需要结合框架版本验证,但计算窗口的思路是一致的。
不定高内容会让计算复杂很多
资讯列表卡片并不总是等高:有的标题一行,有的两行,有的带大图,有的没有图片。如果仍假定固定高度,滚动越往下偏移误差会越明显。
对于不定高列表,可以维护已测量高度和前缀位置:
第 0 项:高度 96,起点 0第 1 项:高度 128,起点 96第 2 项:高度 96,起点 224
未完成测量的卡片先使用预估高度,展示后更新实际尺寸,并修正后续占位位置。此时需要谨慎处理用户正在滚动时的高度修正,避免内容突然跳动。
如果业务允许,先将列表卡片设计为有限几种固定布局,往往比立即实现完整不定高虚拟列表更经济可靠。
图片加载也应和可视区域绑定
只减少卡片节点还不够,如果列表仍然同时请求大量图片,首屏网络和解码成本依然会很高。
对于窗口内卡片,可以正常加载封面;窗口外数据只保留图片地址,不建立实际图片节点。当内容即将进入缓冲区域时再开始准备资源,能够让视图渲染与资源加载保持一致节奏。
滚动定位和返回恢复不能丢失
用户从资讯列表进入详情,再返回列表时,通常期待停留在刚才浏览的位置。虚拟列表不能只记录“当前渲染了哪几条”,而应记录滚动偏移和列表数据上下文:
const listSnapshot = { query: currentQuery, items, scrollTop, page, hasMore};
返回页面时恢复数据和滚动位置,再根据偏移重新计算窗口,用户看到的仍然是原来的列表区域。
性能优化需要用边界换稳定
窗口化渲染带来了计算索引、占位、尺寸测量和恢复定位的额外复杂度,因此不是所有列表都需要使用。只有当列表节点数量、图片数量或滚动体验已经成为明确问题时,它才值得进入页面结构。
在长资讯流中,这种方案的价值非常直接:用户可以继续加载更多内容,但页面承担的实时渲染成本保持在一个较稳定的范围内。