HarmonyOS APP开发:组件复用池与@Reusable深度实践
HarmonyOS APP开发:组件复用池与@Reusable深度实践
📌 核心要点:@Reusable装饰器让组件"退役"后进入复用池而非直接销毁,下次需要同类组件时直接"召回",省去了创建+初始化的开销,是列表滑动场景的性能利器。
一、背景与动机
先想象一个场景:你有一个聊天列表,每个列表项就是一个聊天气泡组件。用户上下滑动列表时,列表项不断被创建和销毁——滑出屏幕的组件被销毁,滑入屏幕的组件被创建。
这有什么问题?问题在于组件的创建并不便宜。一个组件从"无"到"有",要经历:分配内存 → 初始化状态 → 执行aboutToAppear回调 → 测量 → 布局 → 绘制。这一整套流程下来,少则几毫秒,多则十几毫秒。如果列表滑动很快,每帧都要创建好几个新组件,那16.6ms的帧预算很快就会被吃光。
那有没有办法让组件"退役"后不销毁,而是放到一个"蓄水池"里,下次需要同类组件时直接捞出来用?有,这就是组件复用池的核心思想。
HarmonyOS通过@Reusable装饰器实现了这个机制。被@Reusable标记的组件,在从组件树中移除时不会立即销毁,而是进入复用池。当下次需要创建同类型的组件时,框架会优先从复用池中取出一个"退役"组件,重新初始化后直接使用,省去了创建的开销。
这就像工厂里的"临时工"——活忙的时候招来用,活少了先待命,下次有活直接上,不用重新培训。效率提升立竿见影。
二、核心原理
2.1 组件复用池工作机制
flowchart TB
A[组件从组件树移除] --> B{是否标记@Reusable?}
B -->|否| C[直接销毁 💀]
B -->|是| D[进入复用池 🏊]
D --> E[复用池队列]
F[需要创建新组件] --> G{复用池中有同类组件?}
G -->|否| H[创建新组件 🆕]
G -->|是| I[从复用池取出 ♻️]
I --> J[执行aboutToReuse回调]
J --> K[重新初始化状态]
K --> L[加入组件树 ✅]
H --> L
classDef remove fill:#E74C3C,stroke:#C0392B,color:#fff,font-weight:bold
classDef destroy fill:#8E44AD,stroke:#6C3483,color:#fff,font-weight:bold
classDef pool fill:#3498DB,stroke:#2980B9,color:#fff,font-weight:bold
classDef reuse fill:#2ECC71,stroke:#27AE60,color:#fff,font-weight:bold
classDef create fill:#F39C12,stroke:#E67E22,color:#fff,font-weight:bold
class A remove
class C destroy
class D,E pool
class F reuse
class H create
class I,J,K,L reuse
2.2 复用组件的生命周期
普通组件和@Reusable组件的生命周期差异:
| 生命周期回调 | 普通组件 | @Reusable组件 | 说明 |
|---|---|---|---|
aboutToAppear |
首次创建时调用 | 首次创建时调用 | 只在真正创建时触发 |
aboutToReuse |
不存在 | 每次从复用池取出时调用 | 复用时重新初始化状态 |
aboutToRecycle |
不存在 | 进入复用池前调用 | 清理状态、释放临时资源 |
onPageHide |
页面隐藏时调用 | 页面隐藏时调用 | 两者一致 |
aboutToDisappear |
销毁时调用 | 最终销毁时调用 | 复用池清空时才触发 |
2.3 复用池的大小与管理策略
flowchart LR
A[组件进入复用池] --> B{池是否已满?}
B -->|未满| C[直接入池]
B -->|已满| D[销毁最早入池的组件]
D --> E[新组件入池]
classDef enter fill:#3498DB,stroke:#2980B9,color:#fff,font-weight:bold
classDef check fill:#F39C12,stroke:#E67E22,color:#fff,font-weight:bold
classDef action fill:#2ECC71,stroke:#27AE60,color:#fff,font-weight:bold
classDef evict fill:#E74C3C,stroke:#C0392B,color:#fff,font-weight:bold
class A enter
class B check
class C,E action
class D evict
复用池的默认大小是有限的(通常与屏幕可显示的组件数量相关)。当池满时,采用FIFO(先进先出)策略淘汰最早入池的组件。开发者也可以通过配置调整池的大小。
三、代码实战
3.1 基础示例:@Reusable装饰器基本用法
// ✅ 基础用法:标记@Reusable,实现aboutToReuse和aboutToRecycle
@Reusable
@Component
struct ReusableChatBubble {
@Prop message: string = ''
@Prop isMine: boolean = false
@Prop avatar: string = ''
private timestamp: number = 0
// 首次创建时调用
aboutToAppear(): void {
console.info('ChatBubble: 首次创建')
this.timestamp = Date.now()
}
// 从复用池取出时调用 —— 重新初始化状态
aboutToReuse(params: Record<string, Object>): void {
console.info('ChatBubble: 从复用池取出,重新初始化')
// 重新设置时间戳
this.timestamp = Date.now()
// 可以根据params做额外的初始化
if (params['extraData']) {
console.info(`收到额外数据: ${params['extraData']}`)
}
}
// 进入复用池前调用 —— 清理临时状态
aboutToRecycle(): void {
console.info('ChatBubble: 进入复用池,清理状态')
// 清理临时数据,避免下次复用时残留
this.timestamp = 0
}
build() {
Row() {
if (!this.isMine) {
// 对方消息:头像在左
Image(this.avatar || $r('app.media.icon'))
.width(36)
.height(36)
.borderRadius(18)
.margin({ right: 8 })
}
// 消息气泡
Column() {
Text(this.message)
.fontSize(15)
.fontColor(this.isMine ? Color.White : Color.Black)
.padding(12)
.borderRadius(12)
.backgroundColor(this.isMine ? '#07C160' : Color.White)
.shadow({
radius: 2,
color: '#1A000000',
offsetX: 0,
offsetY: 1
})
}
.alignItems(this.isMine ? HorizontalAlign.End : HorizontalAlign.Start)
if (this.isMine) {
// 我的消息:头像在右
Image(this.avatar || $r('app.media.icon'))
.width(36)
.height(36)
.borderRadius(18)
.margin({ left: 8 })
}
}
.width('100%')
.padding({ left: 16, right: 16, top: 4, bottom: 4 })
.justifyContent(this.isMine ? FlexAlign.End : FlexAlign.Start)
}
}
3.2 进阶示例:复用池大小配置与调优
import { hiTraceMeter } from '@kit.PerformanceAnalysisKit'
// 复用统计信息
interface ReuseStats {
createCount: number // 创建次数
reuseCount: number // 复用次数
recycleCount: number // 回收次数
hitRate: number // 复用命中率
}
// 带统计的复用组件
@Reusable
@Component
struct ReusableProductCard {
@Prop productName: string = ''
@Prop price: number = 0
@Prop imageUrl: string = ''
@Prop rating: number = 0
private static stats: ReuseStats = { createCount: 0, reuseCount: 0, recycleCount: 0, hitRate: 0 }
aboutToAppear(): void {
ReusableProductCard.stats.createCount++
console.info(`[复用统计] 创建次数: ${ReusableProductCard.stats.createCount}`)
}
aboutToReuse(params: Record<string, Object>): void {
ReusableProductCard.stats.reuseCount++
const total = ReusableProductCard.stats.createCount + ReusableProductCard.stats.reuseCount
ReusableProductCard.stats.hitRate = ReusableProductCard.stats.reuseCount / total
console.info(`[复用统计] 复用次数: ${ReusableProductCard.stats.reuseCount}, 命中率: ${(ReusableProductCard.stats.hitRate * 100).toFixed(1)}%`)
}
aboutToRecycle(): void {
ReusableProductCard.stats.recycleCount++
}
// 生成星级评分
private getStarText(): string {
const full = Math.floor(this.rating)
const half = this.rating % 1 >= 0.5 ? 1 : 0
return '★'.repeat(full) + (half ? '☆' : '') + ' '.repeat(5 - full - half)
}
build() {
Column() {
// 商品图片
Image(this.imageUrl || $r('app.media.icon'))
.width('100%')
.height(120)
.objectFit(ImageFit.Cover)
.borderRadius({ topLeft: 8, topRight: 8 })
.backgroundColor('#F5F5F5')
// 商品信息
Column() {
Text(this.productName)
.fontSize(14)
.fontWeight(FontWeight.Medium)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Row() {
Text(`¥${this.price.toFixed(2)}`)
.fontSize(16)
.fontColor(Color.Red)
.fontWeight(FontWeight.Bold)
Text(this.getStarText())
.fontSize(12)
.fontColor('#FFB800')
.margin({ left: 8 })
}
.margin({ top: 6 })
}
.padding(8)
.alignItems(HorizontalAlign.Start)
}
.width('100%')
.backgroundColor(Color.White)
.borderRadius(8)
.shadow({ radius: 4, color: '#0D000000', offsetX: 0, offsetY: 2 })
}
}
@Entry
@Component
struct ReusePoolConfigDemo {
@State products: Array<Record<string, Object>> = Array.from({ length: 50 }, (_, i) => ({
name: `商品 ${i + 1}`,
price: Math.random() * 500 + 10,
rating: Math.random() * 2 + 3,
image: ''
}))
build() {
Column() {
Text('组件复用池配置示例')
.fontSize(22)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 16 })
// 网格列表
Grid() {
ForEach(this.products, (product: Record<string, Object>, index?: number) => {
GridItem() {
ReusableProductCard({
productName: product.name as string,
price: product.price as number,
imageUrl: product.image as string,
rating: product.rating as number
})
}
}, (product: Record<string, Object>, index?: number) => `product_${index}`)
}
.columnsTemplate('1fr 1fr')
.rowsGap(12)
.columnsGap(12)
.width('100%')
.layoutWeight(1)
.cachedCount(4) // 预缓存4行,增加复用命中率
// 操作按钮
Row() {
Button('刷新数据')
.layoutWeight(1)
.onClick(() => {
this.products = Array.from({ length: 50 }, (_, i) => ({
name: `新商品 ${i + 1}`,
price: Math.random() * 500 + 10,
rating: Math.random() * 2 + 3,
image: ''
}))
})
}
.width('100%')
.margin({ top: 16 })
}
.width('100%')
.height('100%')
.padding(16)
.backgroundColor('#F5F5F5')
}
}
3.3 完整示例:列表项复用实战
一个完整的聊天列表场景,展示@Reusable在实际项目中的综合运用:
interface ChatMessage {
id: string
content: string
sender: string
isMine: boolean
timestamp: number
type: 'text' | 'image' | 'system'
}
// 复用聊天消息组件
@Reusable
@Component
struct ReusableMessageItem {
@Prop message: ChatMessage | null = null
private loadStartTime: number = 0
aboutToAppear(): void {
this.loadStartTime = Date.now()
}
aboutToReuse(params: Record<string, Object>): void {
// 复用时重置计时
this.loadStartTime = Date.now()
console.info(`[复用] 消息组件复用,节省创建时间约${this.loadStartTime}ms`)
}
aboutToRecycle(): void {
// 清理状态
this.loadStartTime = 0
}
// 格式化时间
private formatTime(ts: number): string {
const date = new Date(ts)
return `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`
}
build() {
if (!this.message) {
return
}
// 系统消息
if (this.message.type === 'system') {
Row() {
Text(this.message.content)
.fontSize(12)
.fontColor(Color.Gray)
.padding(4)
.backgroundColor('#F0F0F0')
.borderRadius(4)
}
.width('100%')
.justifyContent(FlexAlign.Center)
.padding({ top: 8, bottom: 8 })
return
}
// 普通消息
Row() {
if (!this.message.isMine) {
// 对方头像
Image($r('app.media.icon'))
.width(36)
.height(36)
.borderRadius(18)
.margin({ right: 8 })
}
Column() {
// 消息内容
if (this.message.type === 'image') {
Image($r('app.media.icon'))
.width(160)
.height(120)
.borderRadius(8)
.objectFit(ImageFit.Cover)
} else {
Text(this.message.content)
.fontSize(15)
.padding(10)
.borderRadius(8)
.fontColor(this.message.isMine ? Color.White : Color.Black)
.backgroundColor(this.message.isMine ? '#07C160' : Color.White)
}
// 时间戳
Text(this.formatTime(this.message.timestamp))
.fontSize(10)
.fontColor('#999999')
.margin({ top: 4 })
}
.alignItems(this.message.isMine ? HorizontalAlign.End : HorizontalAlign.Start)
if (this.message.isMine) {
// 我的头像
Image($r('app.media.icon'))
.width(36)
.height(36)
.borderRadius(18)
.margin({ left: 8 })
}
}
.width('100%')
.padding({ left: 16, right: 16, top: 4, bottom: 4 })
.justifyContent(this.message.isMine ? FlexAlign.End : FlexAlign.Start)
}
}
@Entry
@Component
struct ChatListReuseDemo {
@State messages: ChatMessage[] = []
aboutToAppear(): void {
// 初始化聊天数据
this.messages = this.generateMessages(100)
}
// 生成模拟聊天数据
private generateMessages(count: number): ChatMessage[] {
const msgs: ChatMessage[] = []
const contents = ['你好!', '在吗?', '今天天气不错', '收到,谢谢!', '好的,没问题',
'稍等一下', '我看看', '马上处理', '已确认', '周末见!']
const senders = ['小明', '小红', '小刚', '我']
for (let i = 0; i < count; i++) {
const isSystem = i % 20 === 0
const senderIndex = Math.floor(Math.random() * senders.length)
const isMine = senders[senderIndex] === '我'
msgs.push({
id: `msg_${i}`,
content: isSystem ? '--- 以下是新消息 ---' : contents[Math.floor(Math.random() * contents.length)],
sender: isSystem ? 'system' : senders[senderIndex],
isMine: isMine,
timestamp: Date.now() - (count - i) * 60000,
type: isSystem ? 'system' : (Math.random() > 0.9 ? 'image' : 'text')
})
}
return msgs
}
build() {
Column() {
// 标题栏
Row() {
Text('聊天列表 - 组件复用实战')
.fontSize(20)
.fontWeight(FontWeight.Bold)
Blank()
Text(`${this.messages.length}条消息`)
.fontSize(14)
.fontColor(Color.Gray)
}
.width('100%')
.padding({ left: 16, right: 16, top: 12, bottom: 12 })
.backgroundColor(Color.White)
Divider().color('#E0E0E0')
// 聊天列表 - 使用@Reusable组件
List({ space: 0 }) {
LazyForEach(
new ChatDataSource(this.messages),
(msg: ChatMessage) => {
ListItem() {
ReusableMessageItem({ message: msg })
}
},
(msg: ChatMessage) => msg.id
)
}
.width('100%')
.layoutWeight(1)
.cachedCount(5)
// 底部输入栏
Row() {
TextInput({ placeholder: '输入消息...' })
.layoutWeight(1)
.height(40)
.borderRadius(20)
.backgroundColor('#F5F5F5')
.padding({ left: 16, right: 16 })
Button('发送')
.height(40)
.margin({ left: 8 })
.onClick(() => {
this.messages.push({
id: `msg_${this.messages.length}`,
content: '新发送的消息',
sender: '我',
isMine: true,
timestamp: Date.now(),
type: 'text'
})
})
}
.width('100%')
.padding({ left: 16, right: 16, top: 8, bottom: 8 })
.backgroundColor(Color.White)
}
.width('100%')
.height('100%')
.backgroundColor('#EDEDED')
}
}
// 数据源类 - 配合LazyForEach使用
class ChatDataSource implements IDataSource {
private messages: ChatMessage[] = []
private listeners: DataChangeListener[] = []
constructor(messages: ChatMessage[]) {
this.messages = messages
}
totalCount(): number {
return this.messages.length
}
getData(index: number): ChatMessage {
return this.messages[index]
}
registerDataChangeListener(listener: DataChangeListener): void {
if (this.listeners.indexOf(listener) < 0) {
this.listeners.push(listener)
}
}
unregisterDataChangeListener(listener: DataChangeListener): void {
const pos = this.listeners.indexOf(listener)
if (pos >= 0) {
this.listeners.splice(pos, 1)
}
}
// 通知数据变更
notifyDataChange(index: number): void {
this.listeners.forEach(listener => {
listener.onDataChange(index)
})
}
notifyDataReload(): void {
this.listeners.forEach(listener => {
listener.onDataReloaded()
})
}
}
四、踩坑与注意事项
坑点1:aboutToRecycle中忘记清理状态导致"脏数据"
组件进入复用池时,如果不清理上一次的状态,下次复用时可能显示上一次的残留数据:
@Reusable
@Component
struct ReusableCard {
@Prop title: string = ''
private tempData: string = '' // 非响应式临时数据
aboutToRecycle(): void {
// ❌ 忘记清理tempData,下次复用可能残留
// this.tempData = ''
// ✅ 必须清理所有临时状态
this.tempData = ''
}
aboutToReuse(params: Record<string, Object>): void {
// ✅ 复用时重新初始化
this.tempData = params['tempData'] as string || ''
}
}
坑点2:@Reusable组件中使用了@StorageLink等全局状态
@Reusable组件被复用时,全局状态(如@StorageLink、@StorageProp)不会自动重置。如果组件依赖全局状态做差异化显示,复用后可能出现数据错乱:
@Reusable
@Component
struct ReusableGlobalStateCard {
// ❌ 危险:全局状态在复用时不会重置
@StorageLink('currentTheme') theme: string = 'light'
aboutToReuse(params: Record<string, Object>): void {
// ✅ 复用时手动同步全局状态
// 注意:AppStorage中的值可能已经变化
this.theme = AppStorage.get<string>('currentTheme') || 'light'
}
}
坑点3:aboutToReuse中执行耗时操作
aboutToReuse在组件重新加入组件树之前调用,如果在这里执行耗时操作,会阻塞渲染:
@Reusable
@Component
struct ReusableHeavyCard {
@Prop dataId: string = ''
private detailData: Record<string, Object> | null = null
aboutToReuse(params: Record<string, Object>): void {
// ❌ 在aboutToReuse中执行耗时加载
// this.detailData = this.loadDetailFromDisk(params['dataId'] as string)
// ✅ 先设置基本状态,异步加载详情
this.dataId = params['dataId'] as string
this.loadDetailAsync(this.dataId)
}
private async loadDetailAsync(id: string): Promise<void> {
// 异步加载,不阻塞渲染
// const result = await this.loadDetailFromDisk(id)
// this.detailData = result
}
}
坑点4:@Reusable与ForEach配合无效
ForEach在数据变更时可能会销毁并重建组件,而不是复用。要让@Reusable真正发挥作用,需要配合LazyForEach使用:
// ❌ ForEach可能绕过复用池
ForEach(this.dataList, (item) => {
ReusableCard({ title: item.name })
}, (item) => item.id)
// ✅ LazyForEach配合@Reusable,复用效果最佳
LazyForEach(this.dataSource, (item) => {
ReusableCard({ title: item.name })
}, (item) => item.id)
坑点5:复用池大小不足导致频繁创建销毁
默认的复用池大小可能不够用,特别是在快速滑动列表时。如果复用命中率低,需要通过cachedCount增加预缓存数量:
// ⚠️ 默认cachedCount可能不够
List() {
LazyForEach(this.dataSource, (item) => {
ListItem() {
ReusableCard({ title: item.name })
}
}, (item) => item.id)
}
// ✅ 根据列表项高度和屏幕尺寸调整cachedCount
.cachedCount(8) // 预缓存8个列表项
坑点6:@Reusable组件的子组件不会自动复用
@Reusable只作用于被标记的组件本身,其子组件不会自动获得复用能力。如果子组件也是高频创建销毁的,需要单独标记@Reusable:
@Reusable
@Component
struct ReusableParent {
build() {
Column() {
// ❌ ChildComponent不会被自动复用
// ChildComponent({ data: this.data })
// ✅ 子组件也标记@Reusable
ReusableChild({ data: this.data })
}
}
}
@Reusable
@Component
struct ReusableChild {
@Prop data: string = ''
aboutToReuse(params: Record<string, Object>): void {
this.data = params['data'] as string || ''
}
build() {
Text(this.data)
}
}
五、HarmonyOS 6适配说明
API差异表
| API/特性 | HarmonyOS 5 | HarmonyOS 6 | 变更说明 |
|---|---|---|---|
| @Reusable | 基础复用功能 | 支持复用池大小配置 | 可自定义每种组件的池大小 |
| aboutToReuse | 无参数类型约束 | 强类型params | 参数类型更安全 |
| 复用池监控 | 无官方API | getReusePoolStats() |
可查询复用池状态 |
| 复用优先级 | FIFO | 支持LRU策略 | 更智能的淘汰策略 |
| 复用跨页面 | 不支持 | 支持跨页面复用 | 同类型组件可跨页面共享复用池 |
行为变更
- 复用池默认大小调整:HarmonyOS 6根据设备内存大小动态调整复用池默认容量,高端设备池更大
- aboutToReuse参数增强:params参数现在支持嵌套对象和数组类型
- 复用池自动扩容:当复用命中率低于50%时,框架会自动扩容复用池
适配代码
import { ComponentUtils } from '@kit.ArkUI'
@Reusable
@Component
struct HarmonyOS6ReusableCard {
@Prop title: string = ''
@Prop subtitle: string = ''
private reuseCount: number = 0
aboutToAppear(): void {
console.info('[ReusableCard] 首次创建')
}
aboutToReuse(params: Record<string, Object>): void {
this.reuseCount++
console.info(`[ReusableCard] 第${this.reuseCount}次复用`)
// HarmonyOS 6: 支持嵌套参数
if (params['config']) {
const config = params['config'] as Record<string, Object>
console.info(`配置项: ${JSON.stringify(config)}`)
}
}
aboutToRecycle(): void {
console.info('[ReusableCard] 进入复用池')
}
build() {
Column() {
Text(this.title)
.fontSize(16)
.fontWeight(FontWeight.Bold)
Text(this.subtitle)
.fontSize(13)
.fontColor(Color.Gray)
.margin({ top: 4 })
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(12)
}
}
@Entry
@Component
struct HarmonyOS6ReuseAdaptation {
@State dataList: Array<Record<string, Object>> = Array.from({ length: 50 }, (_, i) => ({
title: `标题 ${i}`,
subtitle: `副标题 ${i}`,
config: { theme: i % 2 === 0 ? 'light' : 'dark' }
}))
aboutToAppear(): void {
// HarmonyOS 6: 查询复用池状态(降级处理)
try {
// const stats = ComponentUtils.getReusePoolStats('HarmonyOS6ReusableCard')
// console.info(`复用池状态: ${JSON.stringify(stats)}`)
console.info('复用池状态查询可用')
} catch (e) {
console.warn('复用池状态查询不可用,降级处理')
}
}
build() {
Column() {
Text('HarmonyOS 6 组件复用适配')
.fontSize(22)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 16 })
List({ space: 8 }) {
LazyForEach(
new SimpleDataSource(this.dataList),
(item: Record<string, Object>) => {
ListItem() {
HarmonyOS6ReusableCard({
title: item.title as string,
subtitle: item.subtitle as string
})
}
},
(item: Record<string, Object>, index?: number) => `item_${index}`
)
}
.width('100%')
.layoutWeight(1)
.cachedCount(6)
}
.width('100%')
.height('100%')
.padding(16)
.backgroundColor('#F5F5F5')
}
}
// 简易数据源
class SimpleDataSource implements IDataSource {
private data: Array<Record<string, Object>> = []
private listeners: DataChangeListener[] = []
constructor(data: Array<Record<string, Object>>) {
this.data = data
}
totalCount(): number {
return this.data.length
}
getData(index: number): Record<string, Object> {
return this.data[index]
}
registerDataChangeListener(listener: DataChangeListener): void {
if (this.listeners.indexOf(listener) < 0) {
this.listeners.push(listener)
}
}
unregisterDataChangeListener(listener: DataChangeListener): void {
const pos = this.listeners.indexOf(listener)
if (pos >= 0) {
this.listeners.splice(pos, 1)
}
}
}
六、总结
三维度评价表
| 维度 | 评分 | 说明 |
|---|---|---|
| 理论深度 | ⭐⭐⭐⭐ | 复用池机制设计精巧,生命周期管理是核心难点 |
| 实战价值 | ⭐⭐⭐⭐⭐ | 列表场景下效果立竿见影,复用命中率可从0%提升到80%+ |
| 上手难度 | ⭐⭐⭐ | @Reusable用法简单,但aboutToRecycle清理和aboutToReuse初始化容易遗漏 |
组件复用池的本质思想其实很简单:别扔,留着下次用。但魔鬼藏在细节里——什么时候清理?什么时候初始化?怎么和LazyForEach配合?这些都是实战中必须掌握的。
我的建议是:凡是列表项组件,一律加上@Reusable。这是成本最低、收益最高的优化手段之一。加上之后,记得实现aboutToRecycle做清理、aboutToReuse做初始化,配合LazyForEach和cachedCount,你的列表滑动体验会有质的飞跃。
记住:创建一个组件的成本远高于复用一个组件。就像招聘新员工和召回老员工的区别——前者需要走完整的招聘流程,后者只需要说一句"回来上班吧"。
- 点赞
- 收藏
- 关注作者
评论(0)