长列表组件复用体系(第2讲):从滚动白屏到极致渲染
上节课我们搞定了 V2 的状态驱动。有了好的数据模型,如果一拉列表就卡成 PPT,用户体验依然是灾难。几乎所有的大型应用都绕不开长列表,很多开发者第一反应就是“我用 LazyForEach 了呀,为什么还是卡?”。
其实,LazyForEach 只解决了一个问题:不要一次性创建一万个组件节点。但它不负责“组件如何被复用”,也不负责“单个 Item 本身有多重”。如果你在 LazyForEach 里用的 key 是 index,或者没有配置组件复用池,滑动起来照样白屏闪烁,内存照样飙升。今天我们把长列表优化的底裤扒干净。
稳定的Key是正确渲染的基石
在 LazyForEach 的数据源接口中,必须实现 getData(index) 和 keyGenerator。很多新手在这里为了省事,直接返回 index.toString()。这是万恶之源。
当你在列表顶部插入一条新数据时,原本位置为 0 的数据被挤到了 1。如果 key 还是按 index 生成,框架会认为位置 0 还是那个老组件,只是数据变了。但实际上,用户眼里的数据和状态全错位了。图片加载错乱、输入框里的字串行,全是这个原因。
typescript
class BasicDataSource implements IDataSource {
private listeners: DataChangeListener[] = [];
private dataArray: any[] = [];
public totalCount(): number {
return this.dataArray.length;
}
public getData(index: number): any {
return this.dataArray[index];
}
// 核心:必须使用唯一业务标识,绝对不能用 index
public getKey(index: number): string {
return this.dataArray[index].id;
}
// 注册/注销监听器逻辑略
}
使用唯一的业务 id 作为 key。执行结果就是,当你插入或删除数据时,框架能精准识别出哪些组件实例可以留用,哪些需要销毁,组件状态就不会错乱。常见错误是在数据不够唯一时,拿数据内容的哈希当 key,内容一改组件就重建,反而拖垮性能。
开启组件复用@Reusable
仅仅数据源对了还不够。如果你的列表项里包含复杂的图片、文本甚至视频,每次划出屏幕就被销毁,滑回来又要重新构建,开销依然很大。V2 架构下,我们要配合 @Reusable 把组件变成可复用的“空壳”。
只要给自定义组件加上 @Reusable,当它被 LazyForEach 移出可视区时,它不会被彻底回收,而是放入缓存池。下次新数据滑入时,直接拿出这个旧实例,并触发 aboutToReuse 回调,把你需要的新数据塞进去。

typescript
@Reusable
@ComponentV2
struct ListItemCard {
@Local title: string = '';
@Local isSelected: boolean = false;
// 复用发生时,一定要在这里重置所有可变状态
aboutToReuse(params: Record<string, Object>): void {
this.title = params.title as string;
// 重点:上个组件的选中状态千万不要带过来
this.isSelected = false;
}
build() {
Row() {
Text(this.title)
if (this.isSelected) {
Text('已选中').fontColor(Color.Red)
}
}
}
}
看懂了吗?aboutToReuse 就是在这个组件重新登场前,给它重新洗脑,把旧数据洗干净。执行结果就是列表极其丝滑。常见错误就是漏写了 aboutToReuse,或者在里面忘了重置某个内部的 UI 状态(比如弹窗开关、选中态),导致滑动几页后,UI 像幽灵一样串号。
控制缓存数量与布局扁平化
LazyForEach 有一个 cachedCount 属性。设得太小,滑动稍微快一点就露白底;设得太大,内存直接爆炸。通常建议设在 3 到 5 之间,具体看你 item 里有没有大图。
另外,很多开发者为了设计稿还原,在 ListItem 里套了三层 Column 再套两层 Row。长列表滑动时,每一帧都在进行极其复杂的布局测量。能用 RelativeContainer 的地方别用多层嵌套,能用基础组件的,别包一层无意义的容器。把 Item 的布局层级降到最低,才是流畅的根本。
长列表优化不是玄学,核心就三步:给一个绝不重复的业务 ID,开 @Reusable 并在回调里洗脑重置状态,管好缓存数量并压平布局。这三板斧下去,就算几万条数据,也不会再出现卡顿掉帧。下节课,我们把视野拉宽,看看怎么用 GridRow 搞定多端设备的断点适配。
更多推荐
所有评论(0)