Component performance optimization is an eternal theme in HarmonyOS development. When developing a music player, the author found that the frame rate fluctuated significantly during list scrolling. Through analyzing rendering logs, it was discovered that the repeated creation and destruction of numerous components led to resource waste. After in-depth research, the component reuse mechanism of the @Reusable decorator became the key to solving the problem. Combining official documentation with practical experience, this article organizes a systematic learning guide from principles to practice to help developers master this core capability.
I. Core Principles of @Reusable
1.1 Mechanism Analysis
- Reuse Cache Pool: When a marked component is removed from the tree, the component instance and JSView are stored in the cache.
-
Lifecycle Callbacks:
-
aboutToRecycle()
: Called before entering the cache (resource release). -
aboutToReuse(params)
: Called before reuse (parameter update).
-
- Memory Optimization: Avoids repeated creation of layout nodes and reduces render tree diff calculations.
1.2 Capability Boundaries (Restrictions)
⚠️ Note the following usage prohibitions:
// ❌ Wrong example: Builder is not allowed for reuse
@Reusable // Compilation error
@Builder
function buildDialog() { ... }
// ❌ Nested reuse anti-pattern
@Reusable
@Component
struct Parent {
@Reusable // Nested causes dual cache pools
@Component
struct Child { ... }
}
✅ Correct practice: Single-layer reuse + unified cache domain.
II. Practical Scenarios and Code Refactoring
2.1 List Performance Optimization (LazyForEach)
Before optimization: Thousands of data items cause lag when scrolling (1000 creations and destructions).
// Basic list implementation (no reuse)
List {
LazyForEach(data, (item) => {
ListItem { NormalItem() } // Creates new instances on each scroll
})
}
After optimization: Reuse implementation (cache pool reuse rate >95%).
// Refactored into a reusable component
@Reusable
@Component
struct ReusableItem {
@State item: string = ''
aboutToReuse(params: { item: string }) {
this.item = params.item // Only updates data
}
build() {
Row {
Text(item).fontSize(16)
Image($r('app.media.icon')).size(40)
}.padding(10)
}
}
// List usage
List {
LazyForEach(data, (item) => {
ListItem {
ReusableItem({ item: item })
.reuseId('list-item') // Unified cache group
}
})
}.cachedCount(5) // Preloads cache
2.2 Dynamic Updates for Complex Layouts
Scenario: Form components show/hide dynamically (triggered by button clicks).
@Entry
@Component
struct FormDemo {
@State showAdvanced: boolean = false
build() {
Column {
Button('Toggle Advanced Options')
.onClick(() => showAdvanced = !showAdvanced)
if (showAdvanced) {
AdvancedField() // Ordinary component is destroyed and rebuilt each time
.reuseId('advanced-group') // Reuse takes effect
}
}
}
}
@Reusable
@Component
struct AdvancedField {
@State value: string = ''
aboutToReuse() {
this.value = '' // Resets state
}
build() {
// Complex form layout...
}
}
2.3 Multi-container Adaptation (Grid/WaterFlow)
Grid optimization: 3-column waterfall flow (cache strategy).
Grid {
LazyForEach(products, (product) => {
GridItem {
ProductCard(product)
.reuseId(`grid-${product.type}`) // Caches by type
}
})
}.columnsTemplate('1fr 1fr 1fr')
.cachedCount(3) // Cache count matches column count
WaterFlow optimization: Dynamically loads image lists.
WaterFlow {
LazyForEach(images, (img) => {
FlowItem {
ImageLoader(img)
.reuseId('waterflow-item')
}.onAppear(() => {
// Image lazy loading logic
})
})
}.scroller(new Scroller())
.itemSize(120, 150) // Fixed size improves reuse rate
III. Best Practices and Pitfall Avoidance Guide
3.1 Lifecycle Management
@Reusable
@Component
struct VideoPlayer {
private player: MediaPlayer = new MediaPlayer()
aboutToRecycle() {
player.pause() // Releases resources
}
aboutToReuse(params: { url: string }) {
player.setSource(params.url) // Resets data source
}
build() {
// Player UI...
}
}
3.2 State Management Strategies
✅ Recommended pattern:
// Immutable data passing
@Reusable
@Component
struct ArticleCard {
@Prop article: Article // Immutable object
@State localState: number = 0 // Local state
aboutToReuse() {
localState = 0 // Resets local state
}
}
❌ Anti-pattern:
// Error: Shared state causes reuse exceptions
@Reusable
@Component
struct Counter {
static count: number = 0 // Static variables cause state pollution
build() { Text(Counter.count++) }
}
3.3 Performance Monitoring Solutions
// Reuse statistics tool
class ReuseMonitor {
static cache: Map<string, number> = new Map()
static track(component: string, action: 'reuse' | 'recycle') {
const count = this.cache.get(component) || 0
this.cache.set(component, count + 1)
console.log(`[ReuseMonitor] ${component}: ${action} (Total: ${count + 1})`)
}
}
// Called in the lifecycle
aboutToRecycle() {
ReuseMonitor.track('VideoPlayer', 'recycle')
}
aboutToReuse() {
ReuseMonitor.track('VideoPlayer', 'reuse')
}
IV. Common Problem Solutions
4.1 ComponentContent Does Not Support Reuse
Problem: Custom pop-up component crashes.
// Wrong implementation
let content = new ComponentContent(ctx, ReusableDialog) // Directly references reusable component
// Correct solution: Indirectly wrapped by Builder
@Builder
function DialogBuilder() {
ReusableDialog() // Isolates reusable component
}
// Creates using Builder
let content = new ComponentContent(ctx, DialogBuilder)
4.2 Nested Reuse Memory Leak
Diagnosis:
[Memory] Cache size: Parent(10) + Child(20) = 30 instances (expected 10)
Fix:
// Flattened design
@Reusable
@Component
struct CompositeComponent {
build() {
Column {
BaseComponent() // Single cache pool
ExtendedComponent()
}
}
}
4.3 Out-of-sync Data Updates
Scenario: @State variables do not trigger updates.
// Correct pattern: Uses @Prop to pass immutable data
@Reusable
@Component
struct UserProfile {
@Prop user: User // Data changes trigger rebuild
@State theme: Theme = Theme.Light // Local state
aboutToReuse(params: { user: User }) {
// Only updates states that need resetting
this.theme = Theme.Light
}
}
V. Architecture Design Recommendations
5.1 Component Classification Strategy
Type | Reuse Strategy | Cache Cycle |
---|---|---|
High-frequency stable | Global cache (fixed reuseId) | Application lifecycle |
Medium-frequency variable | Page-level cache (shared within page) | Page lifecycle |
Low-frequency dynamic | On-demand cache (manual control) | Component visibility cycle |
5.2 Cache Capacity Planning
// Formula: Cache count = Screen visible count × 1.5 (empirical value)
List { ... }.cachedCount(
Math.ceil(Device.screenHeight / itemHeight) * 1.5
)
5.3 Degradation Scheme Design
// Graceful degradation: Disables reuse when memory is low
@Component
struct AdaptiveList {
private isHighMemory: boolean = Device.memory > 1024 // 1GB
build() {
List {
LazyForEach(data, (item) => {
ListItem {
isHighMemory ? ReusableItem() : NormalItem()
}
})
}
}
}
VI. Summary: The Art of Reuse
Through the practices in this article, we have mastered:
- The core mechanism and lifecycle of @Reusable.
- Component reuse patterns in multiple scenarios (lists/layouts/containers).
- Performance monitoring and problem diagnosis methods.
- Reuse strategy design at the architectural level.
In HarmonyOS development, component reuse is not only a performance optimization method but also an architectural design mindset. Reasonable use of @Reusable, combined with lifecycle management and cache strategies, can increase application performance by 30%-50% (measured data). It is recommended that developers:
- Establish a component reuse repository (standard for basic component libraries).
- Implement reuse coverage monitoring (CI/CD processes).
- Regularly perform memory leak detection (DevEco Studio tools).
Top comments (0)