HarmonyOS 7 组件标了 @ReusableV2,切换父组件却还在重建?全局复用池要放对位置
HarmonyOS 7 组件标了 @ReusableV2,切换父组件却还在重建?全局复用池要放对位置
页面在“列表/卡片”两种区域间来回切换,同一张复杂卡片已经写了 @ReusableV2,日志却总是出现新实例创建。问题不一定是装饰器失效:默认复用池跟着直接父组件走。父组件整体离开组件树时,它自己的池也随之消失;另一个父组件拿不到这批回收实例。API 26 新增的全局复用池正是处理跨父组件复用的工具,但它不是“打开就一定更省内存”的开关。
下面用同一张 ReusableTile 在两个面板之间切换。先看默认池,再把池放到切换期间一直存在的上层组件。
共用的可复用组件
先定义子组件,再定义使用它的父组件。这个顺序不是排版习惯:官方特别提醒,写入 poolAccepts 的组件必须在使用它的装饰器之前完成声明或导入,否则可能触发 Class '...' used before its declaration。
@ReusableV2
@ComponentV2
struct ReusableTile {
@Param label: string = '';
aboutToAppear(): void {
console.info(`tile ${this.label}: appear`);
}
aboutToReuse(): void {
console.info(`tile ${this.label}: reuse`);
}
aboutToRecycle(): void {
console.info(`tile ${this.label}: recycle`);
}
build() {
Row() {
Text(this.label).fontSize(18)
}
.height(96)
.width('100%')
.backgroundColor('#E9F2EF')
}
}
@ComponentV2
struct ListPanel {
build() {
Column() { ReusableTile({ label: '列表卡片' }) }
}
}
@ComponentV2
struct DetailPanel {
build() {
Column() { ReusableTile({ label: '详情卡片' }) }
}
}
案例一:只有子组件可复用,切换面板仍重新创建
这个页面只给 ReusableTile 加了复用装饰器,没有给持续存在的上层组件配置池。连续点击切换按钮,观察 appear、recycle 与 reuse 的日志,不要只凭肉眼觉得“切换变快了”。
@Entry
@ComponentV2
struct BaselinePage {
@Local showList: boolean = true;
build() {
Column({ space: 12 }) {
Button('切换列表/详情')
.onClick(() => { this.showList = !this.showList; })
if (this.showList) {
ListPanel()
} else {
DetailPanel()
}
}
}
}
切换时 ListPanel 与 DetailPanel 是不同的父组件实例。默认复用池归各自父组件持有;一个面板被移除后,其池不能直接供另一个面板取用。这是“写了 @ReusableV2 却看不到跨面板复用”的原因,不要把 aboutToAppear 的出现直接解释成框架从未启用复用。
案例二:在稳定的上层节点接纳这张卡片
把池放在切换时不会被销毁的 PoolOwnerPage 上,并声明它接纳 ReusableTile。这里选 perInstance:当前页面每个实例拥有自己的池,不把不同页面实例的卡片混在一起。
@Entry
@ComponentV2({
reusePool: 'perInstance',
poolAccepts: [ReusableTile],
freezeWhenInactive: false
})
struct PoolOwnerPage {
@Local showList: boolean = true;
build() {
Column({ space: 12 }) {
Button('切换列表/详情')
.onClick(() => { this.showList = !this.showList; })
if (this.showList) {
ListPanel()
} else {
DetailPanel()
}
}
}
}
这段配置的重点不是“池越高越好”,而是池的拥有者要在两个子面板切换期间保持存活。框架回收或获取 ReusableTile 时会向上寻找接纳该类型的全局池,才能让不同父面板有机会共用同一批回收实例。实际日志时序会受到渲染时机影响,应看多次切换后的 reuse 记录与组件创建次数,而不是假定每次点击后立刻打印完全固定的顺序。

为什么不直接选 shared
perInstance 和 shared 解决的是不同的池拥有权:前者随单个拥有组件实例释放;后者可由同一拥有组件类的多个实例共享,只要其中还有实例存活,池可能继续存在。shared 对跨实例复用有价值,却也更需要控制缓存量。官方提供 getReusableInfo 查看池内 count,通过 maxCount 设上限;把上限调低后清理是异步的,短时间里 count 仍可能高于新上限。
| 观察到的现象 | 优先检查 | 不该先做什么 |
|---|---|---|
| 两个父面板间不复用 | 池的拥有者是否在切换时存活 | 继续在子卡片上叠加装饰器 |
| 编译报装饰器参数错误 | reusePool、poolAccepts、freezeWhenInactive 是否配齐 | 把 poolAccepts 传空数组 |
| 编译报先使用后声明 | 可复用组件是否定义在拥有者之前 | 忽略编译错误继续统计性能 |
| 内存不降反升 | shared 池的存活范围、count/maxCount | 宣称复用必然节省内存 |
验证建议
在 API 26 工程中分别运行两页:记录同样的切换次数、appear/recycle/reuse 日志和组件创建次数;然后比较首次进入、来回切换、退出页面三个阶段。发布性能结论前,还要在目标设备上记录帧耗时和内存变化。本文的 API 限制、池查找方向和生命周期说明依据华为 2026-08-29 更新的指南;这些页面代码尚未在当前环境完成 API 26 SDK 编译和真机测量,因此这里只给出可复现步骤,不伪造提速百分比。
官方依据:全局复用:集中化的组件回收与复用。
更多推荐



所有评论(0)