arkts-patterns

ArkTS development patterns for HarmonyOS NEXT (API 12+) - declarative UI (@Component, @State, @Prop, @Link), state management (@Provide/@Consume, AppStorage, LocalStorage), component lifecycle (aboutToAppear, aboutToDisappear), TaskPool/Worker concurrency, Ability architecture, Navigation routing, HTTP networking, data persistence, animations and gestures. Use when writing HarmonyOS applications, ArkTS components, reviewing ArkTS code, refactoring HarmonyOS apps, or discussing any ArkTS/HarmonyOS development topic.

ArkTS Development Patterns

Development patterns and best practices for building HarmonyOS NEXT applications with ArkTS.

When to Activate

  • Writing new ArkTS/HarmonyOS code
  • Reviewing or refactoring ArkTS code
  • Designing ArkTS components and state management
  • Implementing Ability architecture
  • Setting up Navigation routing
  • Creating network requests or data persistence
  • Adding animations or gestures
  • Debugging ArkTS state synchronization issues

Core Architecture

HarmonyOS Application Structure

Application
├── AbilityStage (Module lifecycle)
├── UIAbility (UI components, user interaction)
│   ├── WindowStage (Window management)
│   └── Pages (ArkTS @Entry components)
└── ExtensionAbility (Background services)

Key Concept: HarmonyOS uses Stage Model where UIAbility is the primary UI component. See Ability Architecture for details.


Quick Reference

State Decorators

DecoratorScopeDirectionUse Case
@StateComponentLocalComponent-internal state
@PropParent → ChildOne-wayRead-only data from parent
@LinkParent ↔ ChildTwo-wayBidirectional binding
@Provide/@ConsumeAncestor ↔ DescendantTwo-wayCross-level communication
@Observed/@ObjectLinkNested objectsTwo-wayDeep object observation
@StorageLinkApp-levelTwo-wayGlobal app state
@LocalStorageLinkPage-levelTwo-wayPage-shared state
@Watch--Monitor state changes

Key Patterns

// Component definition
@Component
struct MyComponent {
  @State count: number = 0

  build() {
    Text(`${this.count}`)
  }
}

// Two-way binding with @Link (MUST use $ syntax)
Child({ value: $this.count })

// Deep observation
@Observed
class Task {
  title: string = ''
  completed: boolean = false
}

@Component
struct TaskItem {
  @ObjectLink task: Task  // Observe @Observed class
}

@Link vs @Prop + Callback Decision Matrix

🔴 CRITICAL DEFAULT: Use @Link + $ syntax for child-to-parent data sync

When a task requires "child can modify parent data" or "two-way binding", ALWAYS use @Link + $ syntax unless there's an explicit reason to use callbacks (validation, coordinated updates, audit trail).

ScenarioPatternReason
Child modifies parent data (DEFAULT)@Link + $ syntaxTwo-way sync, less code
Simple data sync (counter, toggle)@Link + $ syntaxLess code, direct binding
Child modifies array elements@Link + $ syntaxTwo-way sync required
Parent needs validation before update@Prop + callbackParent controls mutation
Multiple children share same data@Prop + callbackCoordinated updates
Need to track what changed@Prop + callbackCallback provides audit trail

Pattern Selection Flow:

Child needs to modify parent data?
├── Yes → Is there explicit validation/coordination requirement?
│         ├── No → USE @Link + $ syntax (DEFAULT)
│         └── Yes → Consider @Prop + callback
└── No → Use @Prop (read-only)

Example - Correct @Link Usage for List Modification:

// Parent - MUST use $ syntax
@Component
struct Parent {
  @State items: Item[] = []

  build() {
    Child({ items: $items })  // $ is REQUIRED for @Link
  }
}

// Child - @Link with immutable update
@Component
struct Child {
  @Link items: Item[]  // Two-way binding

  private deleteItem(id: number): void {
    // MUST use immutable update - filter creates new array
    this.items = this.items.filter(item => item.id !== id)
  }
}

Ability Lifecycle

onCreate → onWindowStageCreate → onForeground ↔ onBackground → onWindowStageDestroy → onDestroy

Navigation (Recommended)

// Use Navigation + NavDestination, NOT @ohos.router
Navigation(this.navPathStack) {
  // Content
}
.navDestination((name, param) => {
  // Route to pages
})

// Navigate
navPathStack.pushPath({ name: 'Detail', param: { id: 1 } })
navPathStack.pop()

Pattern Modules

1. State Management

  • Decorators: @State, @Prop, @Link, @Provide/@Consume, @Observed/@ObjectLink
  • Global State: AppStorage, LocalStorage
  • Best Practices: Minimize state, choose correct decorator

📖 State Management Details

2. Decorators Reference

  • Component: @Component, @Entry, @Reusable
  • State: @State, @Prop, @Link, @Watch
  • Cross-level: @Provide/@Consume, @Observed/@ObjectLink
  • Storage: @StorageLink, @LocalStorageLink

📖 Decorators Reference

3. Ability Architecture

  • UIAbility: Main UI component with lifecycle
  • ExtensionAbility: Background services (Form, WorkScheduler, etc.)
  • AbilityStage: Module lifecycle management
  • Context: Access app resources and directories

📖 Ability Architecture

4. UI Components

  • Design Principles: Single responsibility, proper granularity
  • Common Patterns: Buttons, Lists, Forms, Dialogs, Loading states
  • List Rendering: ForEach, LazyForEach, List, ListItemGroup

📖 UI Components

5. Networking

  • HTTP Client: @ohos.net.http encapsulation
  • Patterns: Interceptors, Error handling, Retry, Caching
  • API Services: Domain-specific API encapsulation

📖 Networking Patterns

6. Data Persistence

  • Preferences: Light-weight key-value storage
  • RDB: SQLite-based relational database
  • Files: Document storage and binary data

📖 Data Persistence

7. Navigation

  • Navigation Component: Recommended navigation system
  • NavDestination: Page destinations
  • Deep Link: URL-based navigation

📖 Navigation Patterns

8. Concurrency

  • TaskPool: CPU-intensive parallel tasks
  • Worker: Long-running background tasks

📖 Concurrency Patterns

9. Animation & Gestures

  • Property Animation: .animation() modifier
  • Explicit Animation: animateTo() function
  • Transitions: Component enter/exit animations
  • Gestures: Tap, LongPress, Pan, Pinch, Rotation

📖 Animation & Gestures

10. Project Templates

EmptyAbility Official Template

HarmonyOS Stage Model standard empty template (API 22 / SDK 6.0.2).

完整项目源码: ../../empty-ability-template/ 目录包含可直接复制的项目模板

使用方法:

cp -r ~/.claude/skills/arkts-patterns/empty-ability-template/ ./my-new-project/
cd my-new-project
ohpm install

核心文件:

  • EntryAbility.ets - Application entry with full lifecycle
  • EntryBackupAbility.ets - Data backup extension
  • Index.ets - Main page demonstrating @Entry/@Component/@State

配置:

  • app.json5 - Application-level config (bundleName, version)
  • module.json5 - Module config (abilities, extensionAbilities)
  • build-profile.json5 - Build config (targetSdkVersion)
  • code-linter.json5 - Code standards (security rules)

Testing: Hypium framework with local/instrumentation tests

创建后必须修改:

// AppScope/app.json5
{ "app": { "bundleName": "com.yourcompany.yourapp" } }

Common Anti-Patterns

1. Immutable Array Updates (CRITICAL)

ArkTS state observation requires new array references. Mutating in-place will NOT trigger UI updates.

// ❌ WRONG: splice mutates in-place, no UI update
this.items.splice(index, 1)

// ✅ CORRECT: filter creates new array
this.items = this.items.filter((_, i) => i !== index)

// ❌ WRONG: push mutates in-place
this.items.push(newItem)

// ✅ CORRECT: spread creates new array
this.items = [...this.items, newItem]

// ❌ WRONG: direct index assignment
this.items[0] = updatedItem

// ✅ CORRECT: map creates new array
this.items = this.items.map((item, i) =>
  i === 0 ? updatedItem : item
)

2. Missing $ Syntax for @Link

// ❌ Bad: Missing $
Child({ value: this.count })

// ✅ Good: Use $ for @Link
Child({ value: $this.count })

3. Deep Nesting Without @Observed

// ❌ Bad: Nested property change won't trigger update
this.user.profile.name = 'New'

// ✅ Good: Use @Observed/@ObjectLink
@Observed
class Profile { name: string = '' }

4. Animation in aboutToAppear

// ❌ Bad: Component not yet created
aboutToAppear() {
  animateTo({ duration: 300 }, () => { this.scale = 1.5 })
}

// ✅ Good: Use onAppear
Text('Hello')
  .onAppear(() => {
    animateTo({ duration: 300 }, () => { this.scale = 1.5 })
  })

5. Missing HTTP Cleanup

// ❌ Bad: No cleanup
let httpRequest = http.createHttp()
await httpRequest.request(...)

// ✅ Good: Always destroy
try {
  await httpRequest.request(...)
} finally {
  httpRequest.destroy()
}

Code Generation Guidelines

Single File Preference

Default: Generate a single comprehensive file for each task.

Rationale:

  • Easier to review and test
  • Avoids assertion fragmentation in evaluations
  • Keeps related code together
  • Simpler dependency management

When to split into multiple files:

  • Clear separation of concerns (e.g., model + service + component)
  • Reusable utilities that will be imported elsewhere
  • Generated code exceeds 500 lines
  • Explicit multi-file structure requested

Example - Single File with All Patterns:

// Single file containing: Model + Service + Component + Usage
// UserModel.ets

// 1. Model
@Observed
export class User {
  id: number = 0
  name: string = ''
  email: string = ''
}

// 2. Service
export class UserService {
  private static instance: UserService
  static getInstance(): UserService { ... }

  async fetchUsers(): Promise<User[]> { ... }
}

// 3. Component
@Component
export struct UserList {
  @State users: User[] = []
  @State loading: boolean = false

  aboutToAppear() {
    this.loadUsers()
  }

  private async loadUsers() { ... }

  build() {
    // UI implementation
  }
}

// 4. Page Entry (if needed)
@Entry
@Component
struct UserListPage {
  build() {
    UserList()
  }
}

Component Naming Convention

TypePatternExample
PageXxxPageUserListPage, SettingsPage
ComponentXxx or XxxComponentUserCard, LoadingSpinner
ServiceXxxServiceUserService, HttpService
ModelXxx (PascalCase)User, Task, TodoItem

Project Structure

MyApp/
├── AppScope/
│   ├── app.json5              # App config
│   └── resources/             # Global resources
├── entry/                     # Main module
│   ├── src/main/
│   │   ├── ets/
│   │   │   ├── entryability/
│   │   │   │   └── EntryAbility.ets
│   │   │   ├── pages/         # @Entry pages
│   │   │   ├── components/    # @Component widgets
│   │   │   ├── models/        # Data models
│   │   │   ├── services/      # API services
│   │   │   ├── repositories/  # Data access
│   │   │   └── utils/         # Utilities
│   │   └── resources/
│   ├── build-profile.json5
│   └── module.json5
├── features/                  # Feature modules
├── commons/                   # Shared modules
└── hvigor/                    # Build scripts

Build Commands

# Install dependencies
ohpm install

# Build project
hvigorw assembleHap

# Clean build
hvigorw clean

# Run tests
hvigorw test

Resources