HarmonyOS ArkTS 组件复用详解:理解 @Reusable 装饰器
在 HarmonyOS ArkUI 的开发中,我们常常会封装自定义组件,以便在多个地方重复使用。 但当一个页面中重复创建、销毁大量组件时,例如在动态列表里滑动大量列表项,这会造成性能开销明显增大。这个问题在复杂界面或低端设备上尤其明显。
为了解决这个问题,HarmonyOS 提供了一个非常实用的装饰器: @Reusable 装饰器 ------ 它能让自定义组件具备复用能力 ,最大限度减少组件重复创建和销毁的成本,从而提升渲染性能。华为开发者官网
一、为什么需要组件复用?
在声明式 UI 框架中,每次渲染组件树时:
- 假如组件被移除(例如滚动出屏幕外),默认会销毁实例
- 当它再次显示时又要重新创建,构建新的对象
- 这就造成了大量的 对象创建、布局计算、渲染开销
尤其在以下场景下会出现性能瓶颈:
- 滚动大量列表项(如消息列表、商品列表)
- 动态条件渲染大量结构相同的子组件
- 频繁切换视图导致大量组件反复创建/销毁
这时如果我们能 复用旧的组件实例(并更新其参数),就能省掉这笔开销。
@Reusable 正是为此设计的,它让你标记的自定义组件具备"复用池"能力,从而减少重复创建,提高性能。华为开发者官网
二、@Reusable 到底做了什么?
当你给一个自定义组件加上 @Reusable 装饰器以后:
这个组件从组件树移除时,并不是立即销毁 系统会把它和对应的 JSView 对象缓存到复用池 下一次需要创建同类型组件时 → 会优先从复用池取出旧实例而不是新建 然后把新数据赋值进去重新渲染
这样做的好处:
- 减少重复的 对象创建/销毁
- 降低 内存分配和 GC 频率
- 渲染速度更快、滚动更流畅
- 在复杂界面和高频场景下提升整体性能华为开发者官网
三、使用条件与限制
在实际使用中,有几个关键点你一定要注意:
1. 只能用于自定义组件
@Reusable 不能用于内置组件(如 Text、Button、Image 等),也不能用于简化的 Builder 函数。
要搭配 @Component 一起使用。华为开发者官网
2. 不支持和 @Builder 混用
如果同时用在 Builder 上,会报错;这种组合不被支持。CSDN
3. ComponentContent 不支持
某些用于对话框/弹窗内容的组件结构(ComponentContent)不允许使用 @Reusable。CSDN
4. 复用池和父组件作用域
组件复用通常是"在同一父组件作用域下"发生的。 不同父组件可能维护不同的复用缓存池。bbs.itying.com
四、基本使用示例(真实项目可直接抄)
下面我们做一个 简单的列表项复用示例 :每个列表项本来会频繁创建/销毁,我们用 @Reusable 优化它。
1) 创建可复用组件
typescript
import { Component, Reusable, Prop } from '@ohos.arkui';
@Reusable
@Component
export struct MyListItem {
@Prop item: number = 0
aboutToAppear(): void {
console.log("组件首次创建:", this.item)
}
aboutToReuse(): void {
console.log("组件被复用:", this.item)
}
build() {
Text(`这是第 ${this.item} 项`)
.fontSize(20)
.width("100%")
.textAlign(TextAlign.Center)
}
}
解释:
@Reusable标记这个组件支持复用aboutToAppear()在组件第一次创建并加入组件树前触发aboutToReuse()当组件从复用池重新被拿出来时触发(可以用于初始化/重置)CSDN
2) 在父组件中使用它
scss
@Entry
@Component
export struct Index {
@State arrayNum: number[] = []
aboutToAppear(): void {
this.pushArray()
}
pushArray() {
this.arrayNum = []
for (let index = 0; index < 10; index++) {
this.arrayNum.push(index)
}
}
build() {
Column() {
Row({ space: 20 }) {
Button("清空列表")
.onClick(() => {
this.arrayNum = []
})
Button("创建列表")
.onClick(() => {
this.pushArray()
})
}
List() {
ForEach(this.arrayNum, (item) => {
ListItem() {
MyListItem({ item: item })
}
})
}
.width("100%")
.height("90%")
}
}
}
效果:
- 当你清空列表再创建的时候,不会完全销毁旧组件(除非缓存池满了)
- 控制台会输出
aboutToReuse日志,说明组件被复用了CSDN
五、常见实战技巧 + 注意事项
1. 配合生命周期方法初始化状态
复用组件时,不会自动清理内部 @State 状态,如果你需要它每次看起来像"新创建",可以在 aboutToReuse() 里重置。华为开发者官网
2. 给不同组件分不同的 reuseId
如果父组件里多种复用组件,给它们不同的 reuseId 可以避免互相干扰。
scss
MyListItem({ item: item }).reuseId('MyListItem')
(部分实现中 reuseId 会影响缓存池分组)华为开发者官网
3. 不要滥用
虽然组件复用能提升性能,但也不是所有组件都需要标记。
- 少量不会频繁创建/销毁的页面组件无需加复用
- 纯静态结构组件也没太大意义
六、为什么复用比传统创建/销毁更快?
在声明式框架里,创建组件涉及:
- 实例化对象
- 解析 JSX / build() 逻辑
- 生成虚拟 DOM
- 计算布局和样式
- 在底层生成原生 View
每一步都有性能成本。 复用跳过了"重新创建对象+构建树"的过程,它只是:
- 从缓存池取实例
- 更新属性/状态
- 触发局部刷新
这能显著减少重复工作,尤其在长列表滚动时体现更明显。bbs.itying.com
七、总结(整理成可复用的经验)
| 特性 | 是否支持 |
|---|---|
| 可复用组件 | ✔️ |
| 序列化缓存 | ✔️ |
| 状态池隔离 | ✔️(需要手动管理) |
| 与 Builder 混用 | ❌ |
核心收益:性能优化 > 代码复用逻辑简化 最佳实践:列表项、频繁切换视图的子组件优先标记 @Reusable 华为开发者官网