feat: arkts-development skills
This commit is contained in:
521
arkts-development/references/component-patterns.md
Normal file
521
arkts-development/references/component-patterns.md
Normal file
@@ -0,0 +1,521 @@
|
||||
# ArkUI Component Patterns
|
||||
|
||||
Advanced component patterns and best practices for ArkTS development.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Component Structure](#component-structure)
|
||||
2. [State Management Patterns](#state-management-patterns)
|
||||
3. [Parent-Child Communication](#parent-child-communication)
|
||||
4. [List Optimization](#list-optimization)
|
||||
5. [Custom Components](#custom-components)
|
||||
6. [Conditional Rendering](#conditional-rendering)
|
||||
|
||||
---
|
||||
|
||||
## Component Structure
|
||||
|
||||
### Basic Component
|
||||
|
||||
```typescript
|
||||
@Component
|
||||
struct MyComponent {
|
||||
// Private properties
|
||||
private readonly TAG: string = 'MyComponent';
|
||||
|
||||
// State properties
|
||||
@State isLoading: boolean = false;
|
||||
|
||||
// Props from parent
|
||||
@Prop title: string = '';
|
||||
|
||||
// Lifecycle
|
||||
aboutToAppear(): void {
|
||||
console.log(this.TAG, 'aboutToAppear');
|
||||
}
|
||||
|
||||
// Build method (required)
|
||||
build() {
|
||||
Column() {
|
||||
Text(this.title)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Entry Component (Page)
|
||||
|
||||
```typescript
|
||||
@Entry
|
||||
@Component
|
||||
struct HomePage {
|
||||
@State currentTab: number = 0;
|
||||
|
||||
onPageShow(): void {
|
||||
// Called when page becomes visible
|
||||
}
|
||||
|
||||
onPageHide(): void {
|
||||
// Called when page becomes hidden
|
||||
}
|
||||
|
||||
onBackPress(): boolean {
|
||||
// Return true to prevent default back behavior
|
||||
return false;
|
||||
}
|
||||
|
||||
build() {
|
||||
Navigation() {
|
||||
// Page content
|
||||
}
|
||||
.title('Home')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## State Management Patterns
|
||||
|
||||
### @State - Component Internal State
|
||||
|
||||
```typescript
|
||||
@Component
|
||||
struct Counter {
|
||||
@State count: number = 0;
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
Text(`Count: ${this.count}`)
|
||||
Button('Increment')
|
||||
.onClick(() => { this.count++; })
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### @Prop - One-Way Binding (Parent → Child)
|
||||
|
||||
```typescript
|
||||
// Child component
|
||||
@Component
|
||||
struct DisplayCard {
|
||||
@Prop title: string = '';
|
||||
@Prop value: number = 0;
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
Text(this.title).fontSize(16)
|
||||
Text(`${this.value}`).fontSize(24)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parent component
|
||||
@Entry
|
||||
@Component
|
||||
struct Dashboard {
|
||||
@State temperature: number = 25;
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
DisplayCard({ title: 'Temperature', value: this.temperature })
|
||||
Button('Update')
|
||||
.onClick(() => { this.temperature++; })
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### @Link - Two-Way Binding
|
||||
|
||||
```typescript
|
||||
// Child component
|
||||
@Component
|
||||
struct EditableInput {
|
||||
@Link inputValue: string;
|
||||
|
||||
build() {
|
||||
TextInput({ text: this.inputValue })
|
||||
.onChange((value: string) => {
|
||||
this.inputValue = value;
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Parent component
|
||||
@Entry
|
||||
@Component
|
||||
struct FormPage {
|
||||
@State username: string = '';
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
Text(`Username: ${this.username}`)
|
||||
EditableInput({ inputValue: $username }) // Note: $ prefix
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### @Provide/@Consume - Cross-Level State
|
||||
|
||||
```typescript
|
||||
// Ancestor component
|
||||
@Entry
|
||||
@Component
|
||||
struct App {
|
||||
@Provide('theme') theme: string = 'light';
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
SettingsPage()
|
||||
Button('Toggle Theme')
|
||||
.onClick(() => {
|
||||
this.theme = this.theme === 'light' ? 'dark' : 'light';
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Descendant component (any depth)
|
||||
@Component
|
||||
struct ThemedCard {
|
||||
@Consume('theme') theme: string;
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
Text('Card Content')
|
||||
.fontColor(this.theme === 'light' ? Color.Black : Color.White)
|
||||
}
|
||||
.backgroundColor(this.theme === 'light' ? Color.White : Color.Black)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### @Observed/@ObjectLink - Nested Object Observation
|
||||
|
||||
```typescript
|
||||
// Observable class
|
||||
@Observed
|
||||
class Task {
|
||||
id: number;
|
||||
title: string;
|
||||
completed: boolean;
|
||||
|
||||
constructor(id: number, title: string) {
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
this.completed = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Child component with object link
|
||||
@Component
|
||||
struct TaskItem {
|
||||
@ObjectLink task: Task;
|
||||
|
||||
build() {
|
||||
Row() {
|
||||
Checkbox()
|
||||
.select(this.task.completed)
|
||||
.onChange((value: boolean) => {
|
||||
this.task.completed = value;
|
||||
})
|
||||
Text(this.task.title)
|
||||
.decoration({
|
||||
type: this.task.completed ? TextDecorationType.LineThrough : TextDecorationType.None
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parent component
|
||||
@Entry
|
||||
@Component
|
||||
struct TaskList {
|
||||
@State tasks: Task[] = [
|
||||
new Task(1, 'Buy groceries'),
|
||||
new Task(2, 'Read book')
|
||||
];
|
||||
|
||||
build() {
|
||||
List() {
|
||||
ForEach(this.tasks, (task: Task) => {
|
||||
ListItem() {
|
||||
TaskItem({ task: task })
|
||||
}
|
||||
}, (task: Task) => task.id.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### @StorageLink/@StorageProp - AppStorage Binding
|
||||
|
||||
```typescript
|
||||
// Initialize in EntryAbility
|
||||
AppStorage.setOrCreate('userToken', '');
|
||||
AppStorage.setOrCreate('isLoggedIn', false);
|
||||
|
||||
// Component with storage binding
|
||||
@Entry
|
||||
@Component
|
||||
struct ProfilePage {
|
||||
@StorageLink('userToken') token: string = ''; // Two-way
|
||||
@StorageProp('isLoggedIn') isLoggedIn: boolean = false; // One-way
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
if (this.isLoggedIn) {
|
||||
Text('Welcome!')
|
||||
Button('Logout')
|
||||
.onClick(() => {
|
||||
this.token = '';
|
||||
AppStorage.set('isLoggedIn', false);
|
||||
})
|
||||
} else {
|
||||
Text('Please login')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Parent-Child Communication
|
||||
|
||||
### Events via Callback
|
||||
|
||||
```typescript
|
||||
// Child component
|
||||
@Component
|
||||
struct SearchBar {
|
||||
private onSearch: (query: string) => void = () => {};
|
||||
@State query: string = '';
|
||||
|
||||
build() {
|
||||
Row() {
|
||||
TextInput({ placeholder: 'Search...' })
|
||||
.onChange((value: string) => { this.query = value; })
|
||||
Button('Search')
|
||||
.onClick(() => { this.onSearch(this.query); })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parent component
|
||||
@Entry
|
||||
@Component
|
||||
struct SearchPage {
|
||||
@State results: string[] = [];
|
||||
|
||||
handleSearch(query: string): void {
|
||||
// Perform search
|
||||
this.results = [`Result for: ${query}`];
|
||||
}
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
SearchBar({ onSearch: (q: string) => this.handleSearch(q) })
|
||||
ForEach(this.results, (item: string) => {
|
||||
Text(item)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## List Optimization
|
||||
|
||||
### LazyForEach for Large Lists
|
||||
|
||||
```typescript
|
||||
// Data source implementing IDataSource
|
||||
class MyDataSource implements IDataSource {
|
||||
private data: string[] = [];
|
||||
private listeners: DataChangeListener[] = [];
|
||||
|
||||
constructor(data: string[]) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
totalCount(): number {
|
||||
return this.data.length;
|
||||
}
|
||||
|
||||
getData(index: number): string {
|
||||
return this.data[index];
|
||||
}
|
||||
|
||||
registerDataChangeListener(listener: DataChangeListener): void {
|
||||
this.listeners.push(listener);
|
||||
}
|
||||
|
||||
unregisterDataChangeListener(listener: DataChangeListener): void {
|
||||
const idx = this.listeners.indexOf(listener);
|
||||
if (idx >= 0) {
|
||||
this.listeners.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Component with LazyForEach
|
||||
@Entry
|
||||
@Component
|
||||
struct LargeList {
|
||||
private dataSource: MyDataSource = new MyDataSource(
|
||||
Array.from({ length: 10000 }, (_, i) => `Item ${i}`)
|
||||
);
|
||||
|
||||
build() {
|
||||
List() {
|
||||
LazyForEach(this.dataSource, (item: string, index: number) => {
|
||||
ListItem() {
|
||||
Text(item).fontSize(16).padding(10)
|
||||
}
|
||||
}, (item: string) => item)
|
||||
}
|
||||
.cachedCount(5) // Number of items to cache
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ForEach Key Function
|
||||
|
||||
```typescript
|
||||
// Always provide a unique key function
|
||||
ForEach(this.items, (item: Item) => {
|
||||
ListItem() { ItemCard({ item: item }) }
|
||||
}, (item: Item) => item.id.toString()) // Unique key
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Custom Components
|
||||
|
||||
### Builder Pattern
|
||||
|
||||
```typescript
|
||||
@Entry
|
||||
@Component
|
||||
struct BuilderExample {
|
||||
@Builder
|
||||
CardBuilder(title: string, content: string) {
|
||||
Column() {
|
||||
Text(title).fontSize(20).fontWeight(FontWeight.Bold)
|
||||
Text(content).fontSize(14)
|
||||
}
|
||||
.padding(16)
|
||||
.backgroundColor(Color.White)
|
||||
.borderRadius(8)
|
||||
}
|
||||
|
||||
build() {
|
||||
Column({ space: 16 }) {
|
||||
this.CardBuilder('Card 1', 'Content 1')
|
||||
this.CardBuilder('Card 2', 'Content 2')
|
||||
}
|
||||
.padding(16)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### BuilderParam for Slot Pattern
|
||||
|
||||
```typescript
|
||||
@Component
|
||||
struct Card {
|
||||
@BuilderParam content: () => void = this.defaultContent;
|
||||
|
||||
@Builder
|
||||
defaultContent() {
|
||||
Text('Default Content')
|
||||
}
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
this.content()
|
||||
}
|
||||
.padding(16)
|
||||
.backgroundColor(Color.White)
|
||||
.borderRadius(8)
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
@Entry
|
||||
@Component
|
||||
struct SlotExample {
|
||||
build() {
|
||||
Column() {
|
||||
Card() {
|
||||
Column() {
|
||||
Text('Custom Title')
|
||||
Image($r('app.media.icon'))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Conditional Rendering
|
||||
|
||||
### if/else
|
||||
|
||||
```typescript
|
||||
@Component
|
||||
struct ConditionalExample {
|
||||
@State isLoggedIn: boolean = false;
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
if (this.isLoggedIn) {
|
||||
Text('Welcome back!')
|
||||
Button('Logout')
|
||||
} else {
|
||||
Text('Please login')
|
||||
Button('Login')
|
||||
.onClick(() => { this.isLoggedIn = true; })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Visibility Control
|
||||
|
||||
```typescript
|
||||
@Component
|
||||
struct VisibilityExample {
|
||||
@State showDetails: boolean = false;
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
Text('Summary')
|
||||
Text('Detailed information...')
|
||||
.visibility(this.showDetails ? Visibility.Visible : Visibility.None)
|
||||
Button(this.showDetails ? 'Hide' : 'Show')
|
||||
.onClick(() => { this.showDetails = !this.showDetails; })
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Minimize @State scope** - Keep state as close to where it's used as possible
|
||||
2. **Use @Prop for read-only data** - Prevents accidental modifications
|
||||
3. **Prefer @Link for form inputs** - Enables two-way binding
|
||||
4. **Use LazyForEach for lists > 100 items** - Improves performance
|
||||
5. **Always provide key functions** - Enables efficient list updates
|
||||
6. **Use @Builder for reusable UI blocks** - Reduces duplication
|
||||
7. **Clean up in aboutToDisappear** - Cancel timers, unsubscribe events
|
||||
Reference in New Issue
Block a user