harmonyos-app

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

HarmonyOS Application Development

HarmonyOS应用开发

Core Principles

核心原则

  • ArkTS First — Use ArkTS with strict type safety, no
    any
    or dynamic types
  • Declarative UI — Build UI with ArkUI's declarative components and state management
  • Stage Model — Use modern Stage model (UIAbility), not legacy FA model
  • Distributed by Design — Leverage cross-device capabilities from the start
  • Atomic Services — Consider atomic services and cards for lightweight experiences
  • One-time Development — Design for multi-device adaptation (phone, tablet, watch, TV)

  • 优先使用ArkTS — 使用具有严格类型安全性的ArkTS,禁止使用
    any
    或动态类型
  • 声明式UI — 使用ArkUI的声明式组件和状态管理构建UI
  • Stage模型 — 使用现代化的Stage模型(UIAbility),而非传统的FA模型
  • 分布式设计 — 从项目初期就利用跨设备能力
  • 原子化服务 — 考虑原子化服务和卡片式轻量体验
  • 一次开发多端适配 — 为多设备(手机、平板、手表、电视)设计适配方案

Hard Rules (Must Follow)

硬性规则(必须遵守)

These rules are mandatory. Violating them means the skill is not working correctly.
这些规则为强制性要求。违反规则意味着技能无法正常工作。

No Dynamic Types

禁止使用动态类型

ArkTS prohibits dynamic typing. Never use
any
, type assertions, or dynamic property access.
typescript
// ❌ FORBIDDEN: Dynamic types
let data: any = fetchData();
let obj: object = {};
obj['dynamicKey'] = value;  // Dynamic property access
(someVar as SomeType).method();  // Type assertion

// ✅ REQUIRED: Strict typing
interface UserData {
  id: string;
  name: string;
}
let data: UserData = fetchData();

// Use Record for dynamic keys
let obj: Record<string, string> = {};
obj['key'] = value;  // OK with Record type
ArkTS禁止动态类型。绝不允许使用
any
、类型断言或动态属性访问。
typescript
// ❌ 禁止使用:动态类型
let data: any = fetchData();
let obj: object = {};
obj['dynamicKey'] = value;  // 动态属性访问
(someVar as SomeType).method();  // 类型断言

// ✅ 必须使用:严格类型
interface UserData {
  id: string;
  name: string;
}
let data: UserData = fetchData();

// 使用Record处理动态键
let obj: Record<string, string> = {};
obj['key'] = value;  // 使用Record类型则允许

No Direct State Mutation

禁止直接修改状态

Never mutate @State/@Prop variables directly in nested objects. Use immutable updates.
typescript
// ❌ FORBIDDEN: Direct mutation
@State user: User = { name: 'John', age: 25 };

updateAge() {
  this.user.age = 26;  // UI won't update!
}

// ✅ REQUIRED: Immutable update
updateAge() {
  this.user = { ...this.user, age: 26 };  // Creates new object, triggers UI update
}

// For arrays
@State items: string[] = ['a', 'b'];

// ❌ FORBIDDEN
this.items.push('c');  // UI won't update

// ✅ REQUIRED
this.items = [...this.items, 'c'];
绝不允许在嵌套对象中直接修改@State/@Prop变量。使用不可变更新方式。
typescript
// ❌ 禁止使用:直接修改
@State user: User = { name: 'John', age: 25 };

updateAge() {
  this.user.age = 26;  // UI不会更新!
}

// ✅ 必须使用:不可变更新
updateAge() {
  this.user = { ...this.user, age: 26 };  // 创建新对象,触发UI更新
}

// 数组处理
@State items: string[] = ['a', 'b'];

// ❌ 禁止使用
this.items.push('c');  // UI不会更新

// ✅ 必须使用
this.items = [...this.items, 'c'];

Stage Model Only

仅使用Stage模型

Always use Stage model (UIAbility). Never use deprecated FA model (PageAbility).
typescript
// ❌ FORBIDDEN: FA Model (deprecated)
// config.json with "pages" array
export default {
  onCreate() { ... }  // PageAbility lifecycle
}

// ✅ REQUIRED: Stage Model
// module.json5 with abilities configuration
import { UIAbility } from '@kit.AbilityKit';

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // Modern Stage model lifecycle
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
    windowStage.loadContent('pages/Index');
  }
}
始终使用Stage模型(UIAbility)。绝不允许使用已废弃的FA模型(PageAbility)。
typescript
// ❌ 禁止使用:FA模型(已废弃)
// 包含"pages"数组的config.json
export default {
  onCreate() { ... }  // PageAbility生命周期
}

// ✅ 必须使用:Stage模型
// 包含abilities配置的module.json5
import { UIAbility } from '@kit.AbilityKit';

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // 现代化Stage模型生命周期
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
    windowStage.loadContent('pages/Index');
  }
}

Component Reusability

组件可复用性

Extract reusable UI into @Component. No inline complex UI in build() methods.
typescript
// ❌ FORBIDDEN: Monolithic build method
@Entry
@Component
struct MainPage {
  build() {
    Column() {
      // 200+ lines of inline UI...
      Row() {
        Image($r('app.media.avatar'))
        Column() {
          Text(this.user.name)
          Text(this.user.email)
        }
      }
      // More inline UI...
    }
  }
}

// ✅ REQUIRED: Extract components
@Component
struct UserCard {
  @Prop user: User;

  build() {
    Row() {
      Image($r('app.media.avatar'))
      Column() {
        Text(this.user.name)
        Text(this.user.email)
      }
    }
  }
}

@Entry
@Component
struct MainPage {
  @State user: User = { name: 'John', email: 'john@example.com' };

  build() {
    Column() {
      UserCard({ user: this.user })
    }
  }
}

将可复用UI提取为@Component。禁止在build()方法中编写内联复杂UI。
typescript
// ❌ 禁止使用:单体式build方法
@Entry
@Component
struct MainPage {
  build() {
    Column() {
      // 200+行内联UI...
      Row() {
        Image($r('app.media.avatar'))
        Column() {
          Text(this.user.name)
          Text(this.user.email)
        }
      }
      // 更多内联UI...
    }
  }
}

// ✅ 必须使用:提取组件
@Component
struct UserCard {
  @Prop user: User;

  build() {
    Row() {
      Image($r('app.media.avatar'))
      Column() {
        Text(this.user.name)
        Text(this.user.email)
      }
    }
  }
}

@Entry
@Component
struct MainPage {
  @State user: User = { name: 'John', email: 'john@example.com' };

  build() {
    Column() {
      UserCard({ user: this.user })
    }
  }
}

Quick Reference

快速参考

When to Use What

场景与模式匹配

ScenarioPatternExample
Component-local state@StateCounter, form inputs
Parent-to-child data@PropRead-only child data
Two-way binding@LinkShared mutable state
Cross-component state@Provide/@ConsumeTheme, user context
Persistent statePersistentStorageUser preferences
App-wide stateAppStorageGlobal state
Complex state logic@Observed/@ObjectLinkNested object updates
场景模式示例
组件本地状态@State计数器、表单输入
父到子数据传递@Prop只读子组件数据
双向绑定@Link共享可变状态
跨组件状态@Provide/@Consume主题、用户上下文
持久化状态PersistentStorage用户偏好设置
应用全局状态AppStorage全局状态
复杂状态逻辑@Observed/@ObjectLink嵌套对象更新

State Decorator Selection

状态装饰器选择

@State        → Component owns the state, triggers re-render on change
@Prop         → Parent passes value, child gets copy (one-way)
@Link         → Parent passes reference, child can modify (two-way)
@Provide      → Ancestor provides value to all descendants
@Consume      → Descendant consumes value from ancestor
@StorageLink  → Syncs with AppStorage, two-way binding
@StorageProp  → Syncs with AppStorage, one-way binding
@Observed     → Class decorator for observable objects
@ObjectLink   → Links to @Observed object in parent

@State        → 组件拥有状态,变化时触发重渲染
@Prop         → 父组件传递值,子组件获取副本(单向)
@Link         → 父组件传递引用,子组件可修改(双向)
@Provide      → 祖先组件为所有后代提供值
@Consume      → 后代组件从祖先组件获取值
@StorageLink  → 与AppStorage同步,双向绑定
@StorageProp  → 与AppStorage同步,单向绑定
@Observed     → 可观察对象的类装饰器
@ObjectLink   → 与父组件中的@Observed对象关联

Project Structure

项目结构

Recommended Architecture

推荐架构

MyApp/
├── entry/                          # Main entry module
│   ├── src/main/
│   │   ├── ets/
│   │   │   ├── entryability/       # UIAbility definitions
│   │   │   │   └── EntryAbility.ets
│   │   │   ├── pages/              # Page components
│   │   │   │   ├── Index.ets
│   │   │   │   └── Detail.ets
│   │   │   ├── components/         # Reusable UI components
│   │   │   │   ├── common/         # Common components
│   │   │   │   └── business/       # Business-specific components
│   │   │   ├── viewmodel/          # ViewModels (MVVM)
│   │   │   ├── model/              # Data models
│   │   │   ├── service/            # Business logic services
│   │   │   ├── repository/         # Data access layer
│   │   │   ├── utils/              # Utility functions
│   │   │   └── constants/          # Constants and configs
│   │   ├── resources/              # Resources (strings, images)
│   │   └── module.json5            # Module configuration
│   └── build-profile.json5
├── common/                         # Shared library module
│   └── src/main/ets/
├── features/                       # Feature modules
│   ├── feature_home/
│   └── feature_profile/
└── build-profile.json5             # Project configuration
MyApp/
├── entry/                          # 主入口模块
│   ├── src/main/
│   │   ├── ets/
│   │   │   ├── entryability/       # UIAbility定义
│   │   │   │   └── EntryAbility.ets
│   │   │   ├── pages/              # 页面组件
│   │   │   │   ├── Index.ets
│   │   │   │   └── Detail.ets
│   │   │   ├── components/         # 可复用UI组件
│   │   │   │   ├── common/         # 通用组件
│   │   │   │   └── business/       # 业务专属组件
│   │   │   ├── viewmodel/          # 视图模型(MVVM)
│   │   │   ├── model/              # 数据模型
│   │   │   ├── service/            # 业务逻辑服务
│   │   │   ├── repository/         # 数据访问层
│   │   │   ├── utils/              # 工具函数
│   │   │   └── constants/          # 常量与配置
│   │   ├── resources/              # 资源文件(字符串、图片)
│   │   └── module.json5            # 模块配置
│   └── build-profile.json5
├── common/                         # 共享库模块
│   └── src/main/ets/
├── features/                       # 特性模块
│   ├── feature_home/
│   └── feature_profile/
└── build-profile.json5             # 项目配置

Layer Separation

分层架构

┌─────────────────────────────────────┐
│           UI Layer (Pages)          │  ArkUI Components
├─────────────────────────────────────┤
│         ViewModel Layer             │  State management, UI logic
├─────────────────────────────────────┤
│         Service Layer               │  Business logic
├─────────────────────────────────────┤
│        Repository Layer             │  Data access abstraction
├─────────────────────────────────────┤
│    Data Sources (Local/Remote)      │  Preferences, RDB, Network
└─────────────────────────────────────┘

┌─────────────────────────────────────┐
│           UI层(页面)              │  ArkUI组件
├─────────────────────────────────────┤
│         视图模型层                  │  状态管理、UI逻辑
├─────────────────────────────────────┤
│         服务层                      │  业务逻辑
├─────────────────────────────────────┤
│        数据访问层                   │  数据访问抽象
├─────────────────────────────────────┤
│    数据源(本地/远程)              │  偏好设置、关系型数据库、网络
└─────────────────────────────────────┘

ArkUI Component Patterns

ArkUI组件模式

Basic Component Structure

基础组件结构

typescript
import { router } from '@kit.ArkUI';

@Component
export struct ProductCard {
  // Props from parent
  @Prop product: Product;
  @Prop onAddToCart: (product: Product) => void;

  // Local state
  @State isExpanded: boolean = false;

  // Computed values (use getters)
  get formattedPrice(): string {
    return `¥${this.product.price.toFixed(2)}`;
  }

  // Lifecycle
  aboutToAppear(): void {
    console.info('ProductCard appearing');
  }

  aboutToDisappear(): void {
    console.info('ProductCard disappearing');
  }

  // Event handlers
  private handleTap(): void {
    router.pushUrl({ url: 'pages/ProductDetail', params: { id: this.product.id } });
  }

  private handleAddToCart(): void {
    this.onAddToCart(this.product);
  }

  // UI builder
  build() {
    Column() {
      Image(this.product.imageUrl)
        .width('100%')
        .aspectRatio(1)
        .objectFit(ImageFit.Cover)

      Text(this.product.name)
        .fontSize(16)
        .fontWeight(FontWeight.Medium)

      Text(this.formattedPrice)
        .fontSize(14)
        .fontColor('#FF6B00')

      Button('Add to Cart')
        .onClick(() => this.handleAddToCart())
    }
    .padding(12)
    .backgroundColor(Color.White)
    .borderRadius(8)
    .onClick(() => this.handleTap())
  }
}
typescript
import { router } from '@kit.ArkUI';

@Component
export struct ProductCard {
  // 父组件传入的属性
  @Prop product: Product;
  @Prop onAddToCart: (product: Product) => void;

  // 本地状态
  @State isExpanded: boolean = false;

  // 计算属性(使用getter)
  get formattedPrice(): string {
    return `¥${this.product.price.toFixed(2)}`;
  }

  // 生命周期
  aboutToAppear(): void {
    console.info('ProductCard即将显示');
  }

  aboutToDisappear(): void {
    console.info('ProductCard即将消失');
  }

  // 事件处理函数
  private handleTap(): void {
    router.pushUrl({ url: 'pages/ProductDetail', params: { id: this.product.id } });
  }

  private handleAddToCart(): void {
    this.onAddToCart(this.product);
  }

  // UI构建函数
  build() {
    Column() {
      Image(this.product.imageUrl)
        .width('100%')
        .aspectRatio(1)
        .objectFit(ImageFit.Cover)

      Text(this.product.name)
        .fontSize(16)
        .fontWeight(FontWeight.Medium)

      Text(this.formattedPrice)
        .fontSize(14)
        .fontColor('#FF6B00')

      Button('加入购物车')
        .onClick(() => this.handleAddToCart())
    }
    .padding(12)
    .backgroundColor(Color.White)
    .borderRadius(8)
    .onClick(() => this.handleTap())
  }
}

List with LazyForEach

使用LazyForEach实现列表

typescript
import { BasicDataSource } from '../utils/BasicDataSource';

class ProductDataSource extends BasicDataSource<Product> {
  private products: Product[] = [];

  totalCount(): number {
    return this.products.length;
  }

  getData(index: number): Product {
    return this.products[index];
  }

  addData(product: Product): void {
    this.products.push(product);
    this.notifyDataAdd(this.products.length - 1);
  }

  updateData(index: number, product: Product): void {
    this.products[index] = product;
    this.notifyDataChange(index);
  }
}

@Component
struct ProductList {
  private dataSource: ProductDataSource = new ProductDataSource();

  build() {
    List() {
      LazyForEach(this.dataSource, (product: Product, index: number) => {
        ListItem() {
          ProductCard({ product: product })
        }
      }, (product: Product) => product.id)  // Key generator
    }
    .lanes(2)  // Grid with 2 columns
    .cachedCount(4)  // Cache 4 items for smooth scrolling
  }
}
typescript
import { BasicDataSource } from '../utils/BasicDataSource';

class ProductDataSource extends BasicDataSource<Product> {
  private products: Product[] = [];

  totalCount(): number {
    return this.products.length;
  }

  getData(index: number): Product {
    return this.products[index];
  }

  addData(product: Product): void {
    this.products.push(product);
    this.notifyDataAdd(this.products.length - 1);
  }

  updateData(index: number, product: Product): void {
    this.products[index] = product;
    this.notifyDataChange(index);
  }
}

@Component
struct ProductList {
  private dataSource: ProductDataSource = new ProductDataSource();

  build() {
    List() {
      LazyForEach(this.dataSource, (product: Product, index: number) => {
        ListItem() {
          ProductCard({ product: product })
        }
      }, (product: Product) => product.id)  // 键生成器
    }
    .lanes(2)  // 2列网格布局
    .cachedCount(4)  // 缓存4个项以实现流畅滚动
  }
}

Custom Dialog

自定义对话框

typescript
@CustomDialog
struct ConfirmDialog {
  controller: CustomDialogController;
  title: string = 'Confirm';
  message: string = '';
  onConfirm: () => void = () => {};

  build() {
    Column() {
      Text(this.title)
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 16 })

      Text(this.message)
        .fontSize(16)
        .margin({ bottom: 24 })

      Row() {
        Button('Cancel')
          .onClick(() => this.controller.close())
          .backgroundColor(Color.Gray)
          .margin({ right: 16 })

        Button('Confirm')
          .onClick(() => {
            this.onConfirm();
            this.controller.close();
          })
      }
    }
    .padding(24)
  }
}

// Usage
@Entry
@Component
struct MainPage {
  dialogController: CustomDialogController = new CustomDialogController({
    builder: ConfirmDialog({
      title: 'Delete Item',
      message: 'Are you sure you want to delete this item?',
      onConfirm: () => this.deleteItem()
    }),
    autoCancel: true
  });

  private deleteItem(): void {
    // Delete logic
  }

  build() {
    Button('Delete')
      .onClick(() => this.dialogController.open())
  }
}

typescript
@CustomDialog
struct ConfirmDialog {
  controller: CustomDialogController;
  title: string = '确认';
  message: string = '';
  onConfirm: () => void = () => {};

  build() {
    Column() {
      Text(this.title)
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 16 })

      Text(this.message)
        .fontSize(16)
        .margin({ bottom: 24 })

      Row() {
        Button('取消')
          .onClick(() => this.controller.close())
          .backgroundColor(Color.Gray)
          .margin({ right: 16 })

        Button('确认')
          .onClick(() => {
            this.onConfirm();
            this.controller.close();
          })
      }
    }
    .padding(24)
  }
}

// 使用示例
@Entry
@Component
struct MainPage {
  dialogController: CustomDialogController = new CustomDialogController({
    builder: ConfirmDialog({
      title: '删除项',
      message: '确定要删除此项吗?',
      onConfirm: () => this.deleteItem()
    }),
    autoCancel: true
  });

  private deleteItem(): void {
    // 删除逻辑
  }

  build() {
    Button('删除')
      .onClick(() => this.dialogController.open())
  }
}

State Management Patterns

状态管理模式

ViewModel Pattern

视图模型模式

typescript
// viewmodel/ProductViewModel.ets
import { Product } from '../model/Product';
import { ProductRepository } from '../repository/ProductRepository';

@Observed
export class ProductViewModel {
  products: Product[] = [];
  isLoading: boolean = false;
  errorMessage: string = '';

  private repository: ProductRepository = new ProductRepository();

  async loadProducts(): Promise<void> {
    this.isLoading = true;
    this.errorMessage = '';

    try {
      this.products = await this.repository.getProducts();
    } catch (error) {
      this.errorMessage = `Failed to load: ${error.message}`;
    } finally {
      this.isLoading = false;
    }
  }

  async addProduct(product: Product): Promise<void> {
    const created = await this.repository.createProduct(product);
    this.products = [...this.products, created];
  }
}

// pages/ProductPage.ets
@Entry
@Component
struct ProductPage {
  @State viewModel: ProductViewModel = new ProductViewModel();

  aboutToAppear(): void {
    this.viewModel.loadProducts();
  }

  build() {
    Column() {
      if (this.viewModel.isLoading) {
        LoadingProgress()
      } else if (this.viewModel.errorMessage) {
        Text(this.viewModel.errorMessage)
          .fontColor(Color.Red)
      } else {
        ForEach(this.viewModel.products, (product: Product) => {
          ProductCard({ product: product })
        }, (product: Product) => product.id)
      }
    }
  }
}
typescript
// viewmodel/ProductViewModel.ets
import { Product } from '../model/Product';
import { ProductRepository } from '../repository/ProductRepository';

@Observed
export class ProductViewModel {
  products: Product[] = [];
  isLoading: boolean = false;
  errorMessage: string = '';

  private repository: ProductRepository = new ProductRepository();

  async loadProducts(): Promise<void> {
    this.isLoading = true;
    this.errorMessage = '';

    try {
      this.products = await this.repository.getProducts();
    } catch (error) {
      this.errorMessage = `加载失败:${error.message}`;
    } finally {
      this.isLoading = false;
    }
  }

  async addProduct(product: Product): Promise<void> {
    const created = await this.repository.createProduct(product);
    this.products = [...this.products, created];
  }
}

// pages/ProductPage.ets
@Entry
@Component
struct ProductPage {
  @State viewModel: ProductViewModel = new ProductViewModel();

  aboutToAppear(): void {
    this.viewModel.loadProducts();
  }

  build() {
    Column() {
      if (this.viewModel.isLoading) {
        LoadingProgress()
      } else if (this.viewModel.errorMessage) {
        Text(this.viewModel.errorMessage)
          .fontColor(Color.Red)
      } else {
        ForEach(this.viewModel.products, (product: Product) => {
          ProductCard({ product: product })
        }, (product: Product) => product.id)
      }
    }
  }
}

AppStorage for Global State

使用AppStorage实现全局状态

typescript
// Initialize in EntryAbility
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // Initialize global state
    AppStorage.setOrCreate('isLoggedIn', false);
    AppStorage.setOrCreate('currentUser', null);
    AppStorage.setOrCreate('theme', 'light');
  }
}

// Access in components
@Entry
@Component
struct ProfilePage {
  @StorageLink('isLoggedIn') isLoggedIn: boolean = false;
  @StorageLink('currentUser') currentUser: User | null = null;
  @StorageProp('theme') theme: string = 'light';  // Read-only

  build() {
    Column() {
      if (this.isLoggedIn && this.currentUser) {
        Text(`Welcome, ${this.currentUser.name}`)
      } else {
        Button('Login')
          .onClick(() => {
            // After login
            this.isLoggedIn = true;
            this.currentUser = { id: '1', name: 'John' };
          })
      }
    }
  }
}
typescript
// 在EntryAbility中初始化
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // 初始化全局状态
    AppStorage.setOrCreate('isLoggedIn', false);
    AppStorage.setOrCreate('currentUser', null);
    AppStorage.setOrCreate('theme', 'light');
  }
}

// 在组件中访问
@Entry
@Component
struct ProfilePage {
  @StorageLink('isLoggedIn') isLoggedIn: boolean = false;
  @StorageLink('currentUser') currentUser: User | null = null;
  @StorageProp('theme') theme: string = 'light';  // 只读

  build() {
    Column() {
      if (this.isLoggedIn && this.currentUser) {
        Text(`欢迎,${this.currentUser.name}`)
      } else {
        Button('登录')
          .onClick(() => {
            // 登录后
            this.isLoggedIn = true;
            this.currentUser = { id: '1', name: 'John' };
          })
      }
    }
  }
}

PersistentStorage for Preferences

使用PersistentStorage实现偏好设置

typescript
// Initialize persistent storage
PersistentStorage.persistProp('userSettings', {
  notifications: true,
  darkMode: false,
  language: 'zh-CN'
});

@Entry
@Component
struct SettingsPage {
  @StorageLink('userSettings') settings: UserSettings = {
    notifications: true,
    darkMode: false,
    language: 'zh-CN'
  };

  build() {
    Column() {
      Toggle({ type: ToggleType.Switch, isOn: this.settings.notifications })
        .onChange((isOn: boolean) => {
          this.settings = { ...this.settings, notifications: isOn };
        })

      Toggle({ type: ToggleType.Switch, isOn: this.settings.darkMode })
        .onChange((isOn: boolean) => {
          this.settings = { ...this.settings, darkMode: isOn };
        })
    }
  }
}

typescript
// 初始化持久化存储
PersistentStorage.persistProp('userSettings', {
  notifications: true,
  darkMode: false,
  language: 'zh-CN'
});

@Entry
@Component
struct SettingsPage {
  @StorageLink('userSettings') settings: UserSettings = {
    notifications: true,
    darkMode: false,
    language: 'zh-CN'
  };

  build() {
    Column() {
      Toggle({ type: ToggleType.Switch, isOn: this.settings.notifications })
        .onChange((isOn: boolean) => {
          this.settings = { ...this.settings, notifications: isOn };
        })

      Toggle({ type: ToggleType.Switch, isOn: this.settings.darkMode })
        .onChange((isOn: boolean) => {
          this.settings = { ...this.settings, darkMode: isOn };
        })
    }
  }
}

Navigation Patterns

导航模式

Router Navigation

路由导航

typescript
import { router } from '@kit.ArkUI';

// Navigate to page
router.pushUrl({
  url: 'pages/Detail',
  params: { productId: '123' }
});

// Navigate with result
router.pushUrl({
  url: 'pages/SelectAddress'
}).then(() => {
  // Navigation complete
});

// Get params in target page
@Entry
@Component
struct DetailPage {
  @State productId: string = '';

  aboutToAppear(): void {
    const params = router.getParams() as Record<string, string>;
    this.productId = params?.productId ?? '';
  }
}

// Go back
router.back();

// Replace current page
router.replaceUrl({ url: 'pages/Home' });

// Clear stack and navigate
router.clear();
router.pushUrl({ url: 'pages/Login' });
typescript
import { router } from '@kit.ArkUI';

// 跳转到页面
router.pushUrl({
  url: 'pages/Detail',
  params: { productId: '123' }
});

// 带结果跳转
router.pushUrl({
  url: 'pages/SelectAddress'
}).then(() => {
  // 导航完成
});

// 在目标页面获取参数
@Entry
@Component
struct DetailPage {
  @State productId: string = '';

  aboutToAppear(): void {
    const params = router.getParams() as Record<string, string>;
    this.productId = params?.productId ?? '';
  }
}

// 返回上一页
router.back();

// 替换当前页面
router.replaceUrl({ url: 'pages/Home' });

// 清空栈并跳转
router.clear();
router.pushUrl({ url: 'pages/Login' });

Navigation Component (Recommended for HarmonyOS NEXT)

导航组件(HarmonyOS NEXT推荐)

typescript
@Entry
@Component
struct MainPage {
  @Provide('navPathStack') navPathStack: NavPathStack = new NavPathStack();

  @Builder
  pageBuilder(name: string, params: object) {
    if (name === 'detail') {
      DetailPage({ params: params as DetailParams })
    } else if (name === 'settings') {
      SettingsPage()
    }
  }

  build() {
    Navigation(this.navPathStack) {
      Column() {
        Button('Go to Detail')
          .onClick(() => {
            this.navPathStack.pushPath({ name: 'detail', param: { id: '123' } });
          })
      }
    }
    .navDestination(this.pageBuilder)
    .title('Home')
  }
}

@Component
struct DetailPage {
  @Consume('navPathStack') navPathStack: NavPathStack;
  params: DetailParams = { id: '' };

  build() {
    NavDestination() {
      Column() {
        Text(`Product ID: ${this.params.id}`)
        Button('Back')
          .onClick(() => this.navPathStack.pop())
      }
    }
    .title('Detail')
  }
}

typescript
@Entry
@Component
struct MainPage {
  @Provide('navPathStack') navPathStack: NavPathStack = new NavPathStack();

  @Builder
  pageBuilder(name: string, params: object) {
    if (name === 'detail') {
      DetailPage({ params: params as DetailParams })
    } else if (name === 'settings') {
      SettingsPage()
    }
  }

  build() {
    Navigation(this.navPathStack) {
      Column() {
        Button('跳转到详情页')
          .onClick(() => {
            this.navPathStack.pushPath({ name: 'detail', param: { id: '123' } });
          })
      }
    }
    .navDestination(this.pageBuilder)
    .title('首页')
  }
}

@Component
struct DetailPage {
  @Consume('navPathStack') navPathStack: NavPathStack;
  params: DetailParams = { id: '' };

  build() {
    NavDestination() {
      Column() {
        Text(`产品ID: ${this.params.id}`)
        Button('返回')
          .onClick(() => this.navPathStack.pop())
      }
    }
    .title('详情页')
  }
}

Network Requests

网络请求

HTTP Client

HTTP客户端

typescript
import { http } from '@kit.NetworkKit';

interface ApiResponse<T> {
  code: number;
  message: string;
  data: T;
}

class HttpClient {
  private baseUrl: string = 'https://api.example.com';

  async get<T>(path: string): Promise<T> {
    const httpRequest = http.createHttp();

    try {
      const response = await httpRequest.request(
        `${this.baseUrl}${path}`,
        {
          method: http.RequestMethod.GET,
          header: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${await this.getToken()}`
          },
          expectDataType: http.HttpDataType.OBJECT
        }
      );

      if (response.responseCode === 200) {
        const result = response.result as ApiResponse<T>;
        if (result.code === 0) {
          return result.data;
        }
        throw new Error(result.message);
      }
      throw new Error(`HTTP ${response.responseCode}`);
    } finally {
      httpRequest.destroy();
    }
  }

  async post<T, R>(path: string, data: T): Promise<R> {
    const httpRequest = http.createHttp();

    try {
      const response = await httpRequest.request(
        `${this.baseUrl}${path}`,
        {
          method: http.RequestMethod.POST,
          header: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${await this.getToken()}`
          },
          extraData: JSON.stringify(data),
          expectDataType: http.HttpDataType.OBJECT
        }
      );

      const result = response.result as ApiResponse<R>;
      return result.data;
    } finally {
      httpRequest.destroy();
    }
  }

  private async getToken(): Promise<string> {
    return AppStorage.get('authToken') ?? '';
  }
}

export const httpClient = new HttpClient();

typescript
import { http } from '@kit.NetworkKit';

interface ApiResponse<T> {
  code: number;
  message: string;
  data: T;
}

class HttpClient {
  private baseUrl: string = 'https://api.example.com';

  async get<T>(path: string): Promise<T> {
    const httpRequest = http.createHttp();

    try {
      const response = await httpRequest.request(
        `${this.baseUrl}${path}`,
        {
          method: http.RequestMethod.GET,
          header: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${await this.getToken()}`
          },
          expectDataType: http.HttpDataType.OBJECT
        }
      );

      if (response.responseCode === 200) {
        const result = response.result as ApiResponse<T>;
        if (result.code === 0) {
          return result.data;
        }
        throw new Error(result.message);
      }
      throw new Error(`HTTP ${response.responseCode}`);
    } finally {
      httpRequest.destroy();
    }
  }

  async post<T, R>(path: string, data: T): Promise<R> {
    const httpRequest = http.createHttp();

    try {
      const response = await httpRequest.request(
        `${this.baseUrl}${path}`,
        {
          method: http.RequestMethod.POST,
          header: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${await this.getToken()}`
          },
          extraData: JSON.stringify(data),
          expectDataType: http.HttpDataType.OBJECT
        }
      );

      const result = response.result as ApiResponse<R>;
      return result.data;
    } finally {
      httpRequest.destroy();
    }
  }

  private async getToken(): Promise<string> {
    return AppStorage.get('authToken') ?? '';
  }
}

export const httpClient = new HttpClient();

Distributed Capabilities

分布式能力

Cross-Device Data Sync

跨设备数据同步

typescript
import { distributedKVStore } from '@kit.ArkData';

class DistributedStore {
  private kvManager: distributedKVStore.KVManager | null = null;
  private kvStore: distributedKVStore.SingleKVStore | null = null;

  async init(context: Context): Promise<void> {
    const config: distributedKVStore.KVManagerConfig = {
      context: context,
      bundleName: 'com.example.myapp'
    };

    this.kvManager = distributedKVStore.createKVManager(config);

    const options: distributedKVStore.Options = {
      createIfMissing: true,
      encrypt: false,
      backup: false,
      autoSync: true,  // Auto sync across devices
      kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
      securityLevel: distributedKVStore.SecurityLevel.S1
    };

    this.kvStore = await this.kvManager.getKVStore('myStore', options);
  }

  async put(key: string, value: string): Promise<void> {
    await this.kvStore?.put(key, value);
  }

  async get(key: string): Promise<string | null> {
    try {
      return await this.kvStore?.get(key) as string;
    } catch {
      return null;
    }
  }

  // Subscribe to changes from other devices
  subscribe(callback: (key: string, value: string) => void): void {
    this.kvStore?.on('dataChange', distributedKVStore.SubscribeType.SUBSCRIBE_TYPE_ALL,
      (data: distributedKVStore.ChangeNotification) => {
        for (const entry of data.insertEntries) {
          callback(entry.key, entry.value.value as string);
        }
        for (const entry of data.updateEntries) {
          callback(entry.key, entry.value.value as string);
        }
      }
    );
  }
}
typescript
import { distributedKVStore } from '@kit.ArkData';

class DistributedStore {
  private kvManager: distributedKVStore.KVManager | null = null;
  private kvStore: distributedKVStore.SingleKVStore | null = null;

  async init(context: Context): Promise<void> {
    const config: distributedKVStore.KVManagerConfig = {
      context: context,
      bundleName: 'com.example.myapp'
    };

    this.kvManager = distributedKVStore.createKVManager(config);

    const options: distributedKVStore.Options = {
      createIfMissing: true,
      encrypt: false,
      backup: false,
      autoSync: true,  // 跨设备自动同步
      kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
      securityLevel: distributedKVStore.SecurityLevel.S1
    };

    this.kvStore = await this.kvManager.getKVStore('myStore', options);
  }

  async put(key: string, value: string): Promise<void> {
    await this.kvStore?.put(key, value);
  }

  async get(key: string): Promise<string | null> {
    try {
      return await this.kvStore?.get(key) as string;
    } catch {
      return null;
    }
  }

  // 订阅其他设备的变更
  subscribe(callback: (key: string, value: string) => void): void {
    this.kvStore?.on('dataChange', distributedKVStore.SubscribeType.SUBSCRIBE_TYPE_ALL,
      (data: distributedKVStore.ChangeNotification) => {
        for (const entry of data.insertEntries) {
          callback(entry.key, entry.value.value as string);
        }
        for (const entry of data.updateEntries) {
          callback(entry.key, entry.value.value as string);
        }
      }
    );
  }
}

Device Discovery and Connection

设备发现与连接

typescript
import { distributedDeviceManager } from '@kit.DistributedServiceKit';

class DeviceManager {
  private deviceManager: distributedDeviceManager.DeviceManager | null = null;

  async init(context: Context): Promise<void> {
    this.deviceManager = distributedDeviceManager.createDeviceManager(
      context.applicationInfo.name
    );
  }

  getAvailableDevices(): distributedDeviceManager.DeviceBasicInfo[] {
    return this.deviceManager?.getAvailableDeviceListSync() ?? [];
  }

  startDiscovery(): void {
    const filter: distributedDeviceManager.DiscoveryFilter = {
      discoverMode: distributedDeviceManager.DiscoverMode.DISCOVER_MODE_PASSIVE
    };

    this.deviceManager?.startDiscovering(filter);

    this.deviceManager?.on('discoverSuccess', (data) => {
      console.info(`Found device: ${data.device.deviceName}`);
    });
  }

  stopDiscovery(): void {
    this.deviceManager?.stopDiscovering();
  }
}

typescript
import { distributedDeviceManager } from '@kit.DistributedServiceKit';

class DeviceManager {
  private deviceManager: distributedDeviceManager.DeviceManager | null = null;

  async init(context: Context): Promise<void> {
    this.deviceManager = distributedDeviceManager.createDeviceManager(
      context.applicationInfo.name
    );
  }

  getAvailableDevices(): distributedDeviceManager.DeviceBasicInfo[] {
    return this.deviceManager?.getAvailableDeviceListSync() ?? [];
  }

  startDiscovery(): void {
    const filter: distributedDeviceManager.DiscoveryFilter = {
      discoverMode: distributedDeviceManager.DiscoverMode.DISCOVER_MODE_PASSIVE
    };

    this.deviceManager?.startDiscovering(filter);

    this.deviceManager?.on('discoverSuccess', (data) => {
      console.info(`发现设备: ${data.device.deviceName}`);
    });
  }

  stopDiscovery(): void {
    this.deviceManager?.stopDiscovering();
  }
}

Multi-Device Adaptation

多设备适配

Responsive Layout

响应式布局

typescript
import { BreakpointSystem, BreakPointType } from '../utils/BreakpointSystem';

@Entry
@Component
struct AdaptivePage {
  @StorageProp('currentBreakpoint') currentBreakpoint: string = 'sm';

  build() {
    GridRow({
      columns: { sm: 4, md: 8, lg: 12 },
      gutter: { x: 12, y: 12 }
    }) {
      GridCol({ span: { sm: 4, md: 4, lg: 3 } }) {
        // Sidebar - full width on phone, 1/3 on tablet, 1/4 on desktop
        Sidebar()
      }

      GridCol({ span: { sm: 4, md: 4, lg: 9 } }) {
        // Content - full width on phone, 2/3 on tablet, 3/4 on desktop
        MainContent()
      }
    }
  }
}

// Breakpoint system
export class BreakpointSystem {
  private static readonly BREAKPOINTS: Record<string, number> = {
    'sm': 320,   // Phone
    'md': 600,   // Foldable/Tablet
    'lg': 840    // Desktop/TV
  };

  static register(context: UIContext): void {
    context.getMediaQuery().matchMediaSync('(width >= 840vp)').on('change', (result) => {
      AppStorage.setOrCreate('currentBreakpoint', result.matches ? 'lg' : 'md');
    });

    context.getMediaQuery().matchMediaSync('(width >= 600vp)').on('change', (result) => {
      if (!result.matches) {
        AppStorage.setOrCreate('currentBreakpoint', 'sm');
      }
    });
  }
}

typescript
import { BreakpointSystem, BreakPointType } from '../utils/BreakpointSystem';

@Entry
@Component
struct AdaptivePage {
  @StorageProp('currentBreakpoint') currentBreakpoint: string = 'sm';

  build() {
    GridRow({
      columns: { sm: 4, md: 8, lg: 12 },
      gutter: { x: 12, y: 12 }
    }) {
      GridCol({ span: { sm: 4, md: 4, lg: 3 } }) {
        // 侧边栏 - 手机端全屏,平板端1/3,桌面端1/4
        Sidebar()
      }

      GridCol({ span: { sm: 4, md: 4, lg: 9 } }) {
        // 内容区 - 手机端全屏,平板端2/3,桌面端3/4
        MainContent()
      }
    }
  }
}

// 断点系统
export class BreakpointSystem {
  private static readonly BREAKPOINTS: Record<string, number> = {
    'sm': 320,   // 手机
    'md': 600,   // 折叠屏/平板
    'lg': 840    // 桌面/电视
  };

  static register(context: UIContext): void {
    context.getMediaQuery().matchMediaSync('(width >= 840vp)').on('change', (result) => {
      AppStorage.setOrCreate('currentBreakpoint', result.matches ? 'lg' : 'md');
    });

    context.getMediaQuery().matchMediaSync('(width >= 600vp)').on('change', (result) => {
      if (!result.matches) {
        AppStorage.setOrCreate('currentBreakpoint', 'sm');
      }
    });
  }
}

Testing

测试

Unit Testing

单元测试

typescript
import { describe, it, expect, beforeEach } from '@ohos/hypium';
import { ProductViewModel } from '../viewmodel/ProductViewModel';

export default function ProductViewModelTest() {
  describe('ProductViewModel', () => {
    let viewModel: ProductViewModel;

    beforeEach(() => {
      viewModel = new ProductViewModel();
    });

    it('should load products successfully', async () => {
      await viewModel.loadProducts();

      expect(viewModel.products.length).assertLarger(0);
      expect(viewModel.isLoading).assertFalse();
      expect(viewModel.errorMessage).assertEqual('');
    });

    it('should add product to list', async () => {
      const initialCount = viewModel.products.length;
      const newProduct: Product = { id: 'test', name: 'Test Product', price: 99 };

      await viewModel.addProduct(newProduct);

      expect(viewModel.products.length).assertEqual(initialCount + 1);
    });
  });
}
typescript
import { describe, it, expect, beforeEach } from '@ohos/hypium';
import { ProductViewModel } from '../viewmodel/ProductViewModel';

export default function ProductViewModelTest() {
  describe('ProductViewModel', () => {
    let viewModel: ProductViewModel;

    beforeEach(() => {
      viewModel = new ProductViewModel();
    });

    it('应成功加载产品', async () => {
      await viewModel.loadProducts();

      expect(viewModel.products.length).assertLarger(0);
      expect(viewModel.isLoading).assertFalse();
      expect(viewModel.errorMessage).assertEqual('');
    });

    it('应向列表中添加产品', async () => {
      const initialCount = viewModel.products.length;
      const newProduct: Product = { id: 'test', name: '测试产品', price: 99 };

      await viewModel.addProduct(newProduct);

      expect(viewModel.products.length).assertEqual(initialCount + 1);
    });
  });
}

UI Testing

UI测试

typescript
import { describe, it, expect } from '@ohos/hypium';
import { Driver, ON } from '@ohos.UiTest';

export default function ProductPageUITest() {
  describe('ProductPage UI', () => {
    it('should display product list', async () => {
      const driver = Driver.create();
      await driver.delayMs(1000);

      // Find and verify list exists
      const list = await driver.findComponent(ON.type('List'));
      expect(list).not().assertNull();

      // Verify list items
      const items = await driver.findComponents(ON.type('ListItem'));
      expect(items.length).assertLarger(0);
    });

    it('should navigate to detail on tap', async () => {
      const driver = Driver.create();

      // Find first product card
      const card = await driver.findComponent(ON.type('ProductCard'));
      await card.click();

      await driver.delayMs(500);

      // Verify navigation to detail page
      const detailTitle = await driver.findComponent(ON.text('Product Detail'));
      expect(detailTitle).not().assertNull();
    });
  });
}

typescript
import { describe, it, expect } from '@ohos/hypium';
import { Driver, ON } from '@ohos.UiTest';

export default function ProductPageUITest() {
  describe('ProductPage UI', () => {
    it('应显示产品列表', async () => {
      const driver = Driver.create();
      await driver.delayMs(1000);

      // 查找并验证列表存在
      const list = await driver.findComponent(ON.type('List'));
      expect(list).not().assertNull();

      // 验证列表项
      const items = await driver.findComponents(ON.type('ListItem'));
      expect(items.length).assertLarger(0);
    });

    it('点击后应跳转到详情页', async () => {
      const driver = Driver.create();

      // 查找第一个产品卡片
      const card = await driver.findComponent(ON.type('ProductCard'));
      await card.click();

      await driver.delayMs(500);

      // 验证跳转到详情页
      const detailTitle = await driver.findComponent(ON.text('产品详情'));
      expect(detailTitle).not().assertNull();
    });
  });
}

Checklist

检查清单

markdown
undefined
markdown
undefined

Project Setup

项目设置

  • Stage model used (not FA model)
  • module.json5 properly configured
  • Permissions declared in module.json5
  • Resource files organized (strings, images)
  • 使用Stage模型(而非FA模型)
  • 正确配置module.json5
  • 在module.json5中声明权限
  • 资源文件组织有序(字符串、图片)

Code Quality

代码质量

  • No
    any
    types in codebase
  • All state decorated with proper decorators
  • No direct mutation of @State objects
  • Components extracted for reusability
  • Lifecycle methods used appropriately
  • 代码库中无
    any
    类型
  • 所有状态均使用正确的装饰器
  • 无直接修改@State对象的操作
  • 提取组件以实现复用
  • 合理使用生命周期方法

UI/UX

UI/UX

  • LazyForEach used for long lists
  • Loading states implemented
  • Error handling with user feedback
  • Multi-device layouts with GridRow/GridCol
  • Accessibility attributes added
  • 长列表使用LazyForEach
  • 实现加载状态
  • 错误处理并提供用户反馈
  • 使用GridRow/GridCol实现多设备布局
  • 添加无障碍属性

State Management

状态管理

  • Clear state ownership (component vs global)
  • @Observed/@ObjectLink for nested objects
  • PersistentStorage for user preferences
  • AppStorage for app-wide state
  • 明确状态所有权(组件级 vs 全局)
  • 嵌套对象使用@Observed/@ObjectLink
  • 用户偏好设置使用PersistentStorage
  • 应用全局状态使用AppStorage

Performance

性能

  • Images optimized and cached
  • Unnecessary re-renders avoided
  • Network requests with proper error handling
  • Background tasks properly managed
  • 图片优化并缓存
  • 避免不必要的重渲染
  • 网络请求包含正确的错误处理
  • 合理管理后台任务

Testing

测试

  • Unit tests for ViewModels
  • UI tests for critical flows
  • Edge cases covered

---
  • 视图模型包含单元测试
  • 关键流程包含UI测试
  • 覆盖边缘场景

---

See Also

相关链接

  • reference/arkts.md — ArkTS language guide and restrictions
  • reference/arkui.md — ArkUI components and styling
  • reference/stage-model.md — Stage model architecture
  • reference/distributed.md — Distributed capabilities guide
  • templates/project-structure.md — Project template
  • reference/arkts.md — ArkTS语言指南与限制
  • reference/arkui.md — ArkUI组件与样式
  • reference/stage-model.md — Stage模型架构
  • reference/distributed.md — 分布式能力指南
  • templates/project-structure.md — 项目模板