2-10.【ArkTS】ArkTS 如何避免不必要的重渲染?大列表如何优化?LazyForEach 的底层机制?

3 阅读3分钟

在 ArkTS 开发中,性能优化的核心目标是:减少不必要的 build() 执行以及降低单次渲染的计算开销

对于大型应用,尤其是复杂列表,ArkUI 提供了一套从语法到引擎底层的深度优化方案。


1. 如何避免不必要的重渲染?

由于 ArkTS 是数据驱动的,任何状态的改变都可能触发组件重绘。要做到“精准更新”,可以从以下三个维度入手:

A. 状态拆分与“最小化联动”

不要在一个巨大的 @State 对象中存储所有数据。

  • 风险: 如果你修改了 user.age,由于 user 是一个对象,所有引用 user 的组件都会被标记为“待更新”。
  • 优化: 将频繁变化的字段拆分为独立的 @State 变量,或者使用 @Observed / @ObjectLink 进行嵌套对象的精准监听。

B. 使用 @Watch 拦截无效更新

通过 @Watch 装饰器,你可以在状态改变时先执行逻辑判断。

TypeScript

@State @Watch('onCountChange') count: number = 0;

onCountChange() {
  // 只有当 count 达到某个阈值时,才触发后续逻辑,避免无谓的 UI 抖动
}

C. 善用 const 与不可变布局

build() 内部,尽量避免复杂的逻辑计算(如数组过滤、排序)。这些计算应该在数据源头完成,build() 只负责读取结果。


2. 大列表优化:LazyForEach 的底层机制

传统的 ForEach 会在组件加载时一次性创建所有子组件。如果你有 1000 条数据,它就会创建 1000 个原生节点,这会导致内存飙升和明显的启动卡顿。

LazyForEach 的核心逻辑:按需实例化

LazyForEach 并不直接渲染所有数据,它维护了一个**“可视区域窗口”**。

  1. 数据懒加载: 只有滚动到屏幕内(或预加载范围内)的数据,才会触发子组件的 aboutToAppearbuild()
  2. 组件复用(ID 机制): 它依赖 keyGenerator 为每条数据生成唯一 ID。当数据顺序变化时,ArkUI 通过 ID 匹配旧节点,只移动位置而不销毁重建。
  3. 缓存池(Caching): 它会预先渲染屏幕上下方少量的节点(由 cachedCount 指定),确保用户滑动时不会看到白屏。

3. 深度优化策略:大列表的“终极方案”

即使使用了 LazyForEach,如果每个列表项(Item)过于复杂,滑动依然可能掉帧。

优化手段原理效果
组件复用 (@Reusable)在节点滑出屏幕时不销毁,而是放入复用池。滑入新数据时直接“抹去旧数据、换上新数据”。消除频繁创建/销毁 C++ 原生节点的开销。
减少布局嵌套尽量使用 RelativeContainer 代替多层嵌套的 Column/Row降低 UI 树的深度,减轻布局引擎的计算压力。
预取数据 (Prefetch)在滑动过程中通过 IDataSource 提前触发下一页网络请求。消除滑动到底部时的“等待加载”感。
固定宽高如果 Item 宽高固定,在容器上指定 getItemRect避免渲染引擎为了计算总长度而反复测量所有子项。

总结:性能优化的金字塔

  1. 塔基: 正确使用 LazyForEachIDataSource(解决“多”的问题)。
  2. 塔身: 使用 @Reusable 和精简布局(解决“重”的问题)。
  3. 塔尖: 利用 @ObjectLink 实现局部精准刷新(解决“频”的问题)。

特别注意: ArkTS 的 LazyForEach 必须配合继承了 IDataSource 接口的数据类使用,否则它无法感知数据的局部增删,会退化成全量刷新。