华为鸿蒙(HarmonyOS)开发入门与实战手册

本文档面向有一定编程基础的开发者,系统整理 HarmonyOS 应用开发的核心知识与实战技巧,涵盖开发环境、项目结构、ArkTS 语言基础、常用组件、网络数据处理、本地存储等日常高频场景,配有完整示例与注释。


前言:HarmonyOS 生态概览

什么是 HarmonyOS

HarmonyOS(鸿蒙操作系统)是华为推出的面向全场景(手机、平板、智慧屏、可穿戴设备、车机、IoT 设备等)的分布式操作系统。其核心理念是”一次开发,多端部署”——同一套代码可以运行在不同设备形态上。

核心术语

术语 说明
OpenHarmony 开放原子开源基金会托管的开源项目,是 HarmonyOS 的开源底座
HarmonyOS 华为基于 OpenHarmony 商业化的操作系统品牌
ArkUI HarmonyOS 的声明式 UI 开发框架,支持 ArkTS 和 JavaScript 两种开发范式
ArkTS 基于 TypeScript 扩展的编程语言,是 HarmonyOS 推荐的声明式 UI 开发语言
Stage 模型 HarmonyOS 3.0 引入的应用编程模型,推荐使用
FA(Feature Ability) 早期版本使用的 Page 能力模型
FA/PA Feature Ability 和 Particle Ability,分别代表有界面的能力和无界面的服务
HAP HarmonyOS Application Package,应用安装包格式
HSP HarmonyOS Shared Package,共享包格式,用于模块间代码共享
HAR Harmony Archive,静态共享包,类似 Android AAR

技术架构

┌─────────────────────────────────────────────┐
│           应用层(Application)               │
│   FA / PA / Service Extension Ability       │
├─────────────────────────────────────────────┤
│           框架层(Framework)                 │
│   ArkUI / ArkTS / Java UI Framework         │
├─────────────────────────────────────────────┤
│           系统层(System)                    │
│   分布式软总线 / 数据管理 / 任务调度           │
├─────────────────────────────────────────────┤
│           基础层(Engine)                   │
│   ArkCompiler / JavaScript Engine            │
├─────────────────────────────────────────────┤
│           内核层(Kernel)                   │
│   Linux Kernel / LiteOS                      │
└─────────────────────────────────────────────┘

开发环境搭建

所需工具

  • DevEco Studio:华为官方 IDE,基于 IntelliJ IDEA 定制,是开发 HarmonyOS 应用的主力工具
  • HarmonyOS SDK:包含编译工具链、模拟器、系统镜像等
  • Node.js:部分构建工具依赖(如果需要用到前端构建流程)
  • JDK:DevEco Studio 内置 JDK 17,通常不需要单独安装

安装步骤

第一步:下载 DevEco Studio

访问华为开发者官网(https://developer.huawei.com/consumer/cn/develop/deveco-studio)下载最新版本的 DevEco Studio 安装包,Windows/macOS/Linux 均支持。

第二步:安装并启动

双击安装包,按向导完成安装。首次启动时会引导配置 SDK:

  • 选择 SDK 安装路径(建议使用默认路径或 SSD 所在磁盘)
  • 选择需要安装的 SDK 组件(手机、平板、智慧屏、IoT 设备等按需选择)
  • 建议勾选 “自动下载更新”,保持工具链最新

第三步:配置模拟器或真机调试

# 查看 DevEco Studio 中已配置的模拟器
# 路径:DevEco Studio -> Device Manager -> Emulator

# 真机调试需要在设备上开启开发者模式
# 设置 -> 关于手机 -> 连续点击版本号 7 次开启开发者选项
# 设置 -> 系统和更新 -> 开发人员选项 -> 开启 USB 调试

第四步:验证环境

// 新建项目后,点击 Run 按钮编译并运行
// 如果模拟器/真机正常启动运行,说明环境配置成功

环境问题排查

# 问题1:SDK 下载失败
# 解决:检查网络,或手动下载 SDK 后通过 DevEco Studio 配置本地 SDK 路径

# 问题2:模拟器无法启动
# 解决:检查电脑是否支持虚拟化技术(Intel VT-x / AMD-V)
#       在 BIOS 中开启虚拟化支持

# 问题3:构建失败,提示签名问题
# 解决:在 project.json 或 module.json5 中配置签名信息
#       或在 DevEco Studio 中:File -> Project Structure -> Signing Config

项目结构

Stage 模型项目结构

从 HarmonyOS 3.0 起,推荐使用 Stage 模型。以下是典型的 Stage 模型项目结构:

entry/
├── src/
│   └── main/
│       ├── ets/                          # ArkTS/ArkUI 代码目录
│       │   ├── ability/
│       │   │   ├── EntryAbility.ets      # 入口 UIAbility(类似 Android MainActivity)
│       │   │   └── FormAbility.ets       # 卡片 Ability(可选)
│       │   ├── pages/
│       │   │   ├── Index.ets             # 首页(由 UIAbility 加载的第一个页面)
│       │   │   ├── Detail.ets            # 详情页示例
│       │   │   └── Mine.ets              # 我的页面示例
│       │   ├── components/               # 可复用组件目录
│       │   │   ├── TitleBar.ets          # 顶部导航栏组件
│       │   │   ├── ProductCard.ets       # 商品卡片组件
│       │   │   └── LoadingView.ets       # 加载中组件
│       │   ├── services/                 # 服务层(数据请求、业务逻辑)
│       │   │   ├── HttpService.ets       # 网络请求服务
│       │   │   └── UserService.ets       # 用户相关服务
│       │   ├── models/                   # 数据模型定义
│       │   │   └── UserModel.ets         # 用户数据模型
│       │   ├── utils/                    # 工具函数
│       │   │   ├── Logger.ets            # 日志工具
│       │   │   └── StorageHelper.ets     # 本地存储工具
│       │   └── views/                    # 页面级视图(可选)
│       │       ├── HomeView.ets
│       │       └── SearchView.ets
│       ├── module.json5                  # 模块配置文件
│       └── resources/                    # 资源目录
│           ├── base/
│           │   ├── element/              # 资源元素(颜色、字符串、图片路径等)
│           │   ├── media/                # 媒体资源(图片、音频、视频)
│           │   └── profile/              # 配置文件(main_pages.json 页面路由配置)
│           └── en_US/                    # 英文资源(可选)
│           └── zh_CN/                    # 中文资源(可选)
├── build-profile.json5                   # 构建配置文件
├──hvigor/                               # Hvigor 构建工具配置
└── oh-package.json5                      # 依赖管理文件(npm 包依赖)

各目录作用详解

目录/文件 作用
EntryAbility.ets 应用入口 Ability,类似 Android 的 MainActivity,负责加载首页
pages/ 存放所有页面文件,每个 .ets 文件对应一个页面
components/ 可复用组件,类似 React/Vue 组件,封装 UI 和逻辑
services/ 业务逻辑层,封装网络请求、数据处理等,供页面调用
models/ 数据模型定义,使用 TypeScript 接口描述数据结构
resources/base/element/ 存放颜色值、字符串等资源
resources/base/profile/main_pages.json 页面路由配置,定义每个页面的路径
module.json5 模块级配置,包含 Ability 配置、依赖声明等

配置文件解析

main_pages.json(页面路由配置)

{
  "src": [
    {
      "name": "Index",          // 页面名称,路由时使用
      "srcFile": "pages/Index", // 页面文件路径(.ets 后缀省略)
      "routerMode": "single"    // single 表示单例模式(路由到同一页面不会重复创建)
    },
    {
      "name": "Detail",
      "srcFile": "pages/Detail",
      "routerMode": "single"
    },
    {
      "name": "Mine",
      "srcFile": "pages/Mine",
      "routerMode": "single"
    }
  ]
}

module.json5(模块配置)

{
  "module": {
    "name": "entry",                    // 模块名称
    "type": "entry",                    // entry 表示应用入口模块
    "description": "$string:module_desc",
    "mainElement": "EntryAbility",       // 应用主 Ability
    "deviceTypes": [                    // 支持的设备类型
      "phone",
      "tablet"
    ],
    "deliveryWithInstall": true,
    "installationFree": false,
    "pages": "$profile:main_pages",    // 关联页面路由配置
    "abilities": [
      {
        "name": "EntryAbility",         // Ability 名称
        "srcEntry": "./ets/ability/EntryAbility.ets",
        "description": "$string:EntryAbility_desc",
        "icon": "$media:icon",          // 应用图标
        "label": "$string:EntryAbility_label",
        "startWindowIcon": "$media:icon",
        "startWindowBackground": "$color:start_window_background",
        "exported": true,                // 是否支持其他应用拉起
        "skills": [
          {
            "entities": ["entity.system.home"],
            "actions": ["action.system.home"]
          }
        ]
      }
    ]
  }
}

ArkTS 语言基础

ArkTS 简介

ArkTS 是基于 TypeScript 扩展的编程语言,专为 HarmonyOS 的 ArkUI 声明式 UI 框架设计。它继承了 TypeScript 的类型系统和面向对象特性,同时针对声明式 UI 开发做了扩展,增加了 @State@Prop@Link 等装饰器,用于实现响应式数据绑定。

// ArkTS 代码示例(Index.ets)
// @State 是 ArkTS 的状态装饰器,被装饰的变量值改变时,UI 自动更新
@State message: string = 'Hello HarmonyOS'

@Entry                          // @Entry 装饰器,表示这是页面的入口组件
@Component                      // @Component 装饰器,表示这是一个 UI 组件
struct Index {
  @State count: number = 0      // 组件内私有状态

  build() {                     // build() 是声明式 UI 的核心,所有 UI 在这里用 DSL 描述
    Column() {                  // Column:纵向布局容器(类似 FlexDirection.Column)
      Text(this.message)        // Text:文本组件
        .fontSize(50)
        .fontWeight(FontWeight.Bold)

      Text(`计数器: ${this.count}`)
        .fontSize(30)

      Row() {                   // Row:横向布局容器
        Button('增加')
          .onClick(() => {      // 事件绑定:点击按钮时触发回调
            this.count++        // 修改 @State 变量,UI 自动刷新
          })

        Button('减少')
          .onClick(() => {
            this.count--
          })
      }
      .margin({ top: 20 })
      .justifyContent(FlexAlign.Center)
      .width('100%')
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
  }
}

变量声明与类型

// 基本类型
let name: string = 'HarmonyOS'
let version: number = 4.0
let isStable: boolean = true

// 数组
let versions: string[] = ['2.0', '3.0', '4.0']
let scores: Array<number> = [95, 88, 76]

// 对象
interface User {
  id: number
  name: string
  email?: string              // 可选属性
  readonly createdAt: number  // 只读属性
}

let user: User = {
  id: 1,
  name: '张三',
  createdAt: Date.now()
}

// 泛型
function getValue<T>(key: string, defaultValue: T): T {
  // ...
  return defaultValue
}

// 联合类型
type Status = 'pending' | 'success' | 'error'
let status: Status = 'success'

// 字面量类型
let method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET'

函数与箭头函数

// 普通函数
function add(a: number, b: number): number {
  return a + b
}

// 箭头函数
const multiply = (a: number, b: number): number => a * b

// 带回调参数的函数
function fetchData(callback: (data: string) => void): void {
  callback('data from server')
}

// 可选参数
function greet(name: string, greeting?: string): string {
  return `${greeting ?? 'Hello'}, ${name}!`
}
greet('World')            // Hello, World!
greet('World', '你好')     // 你好, World!

// 默认参数
function connect(host: string = 'localhost', port: number = 8080): void {
  console.info(`连接 ${host}:${port}`)
}

类与接口

// 接口定义
interface Product {
  id: number
  name: string
  price: number
  getDiscountPrice(discount: number): number
}

// 抽象类
abstract class BaseService {
  protected baseUrl: string = ''

  constructor(baseUrl: string) {
    this.baseUrl = baseUrl
  }

  // 抽象方法:子类必须实现
  abstract fetchData(): Promise<string>

  // 具体方法
  protected log(message: string): void {
    console.info(`[Service] ${message}`)
  }
}

// 实现类
class ProductService extends BaseService {
  async fetchData(): Promise<string> {
    this.log('正在获取产品数据...')
    return await new Promise(resolve => {
      setTimeout(() => resolve('产品数据'), 1000)
    })
  }
}

装饰器详解

ArkTS 装饰器是实现声明式响应式 UI 的核心机制,以下是日常开发中最常用的装饰器:

// @State:组件内私有状态(最常用)
// 只能在自己组件内部修改,修改后 UI 自动更新
@State count: number = 0

// @Prop:从父组件接收属性(单向数据流)
// 父组件修改值时,子组件跟着变;子组件修改不影响父组件
@Prop title: string = ''

// @Link:与父组件双向绑定
// 子组件修改值时,父组件也跟着变(双向数据流)
@Link userName: string

// @StorageLink:关联 AppStorage(全局应用状态)
// 数据变化自动同步到 AppStorage,AppStorage 变化自动同步回组件
@StorageLink('theme') theme: string = 'light'

// @StorageProp:单向关联 AppStorage
@StorageProp('language') language: string = 'zh-CN'

// @Consume:消费由 @Provide 提供的祖先组件数据
@Consume('cartCount') cartCount: number = 0

// @Provide:在祖先组件中提供数据
@Provide('cartCount') cartCount: number = 0

// @Watch:监听状态变化并执行回调
@State @Watch('onCountChange') count: number = 0

onCountChange() {
  console.info(`count 变化了: ${this.count}`)
}

常用 UI 组件

布局容器

Column / Row / Stack

// Column:纵向布局(从上到下排列)
@Entry
@Component
struct ColumnExample {
  build() {
    Column() {
      Text('第一行')
      Text('第二行')
      Text('第三行')
    }
    .width('100%')
    .height('100%')
    .padding(20)
    .justifyContent(FlexAlign.Start)   // 垂直方向对齐方式
    .alignItems(HorizontalAlign.Start) // 水平方向对齐方式
    .gap(10)                            // 元素间距
  }
}

// Row:横向布局(从左到右排列)
@Entry
@Component
struct RowExample {
  build() {
    Row() {
      Text('图标')
      Text('标题')
      Blank()           // Blank:占据剩余空间
      Text('更多')
    }
    .width('100%')
    .height(50)
    .padding(10)
    .alignItems(VerticalAlign.Center)
  }
}

// Stack:层叠布局(后面的元素叠加在前面的元素之上)
@Entry
@Component
struct StackExample {
  build() {
    Stack() {
      Image('/common/images/background.jpg')  // 背景图片
        .width('100%')
        .height('100%')

      Text('浮在图片上的文字')
        .fontSize(24)
        .fontColor('#FFFFFF')
        .position({ x: 20, y: 20 })  // 绝对定位
    }
    .width('100%')
    .height(300)
  }
}

Flex 布局

// Flex:弹性盒布局,支持更复杂的排列场景
@Entry
@Component
struct FlexExample {
  build() {
    Flex({
      direction: FlexDirection.Row,       // 主轴方向:横向
      justifyContent: FlexAlign.SpaceBetween, // 两端对齐
      alignItems: ItemAlign.Center,       // 交叉轴居中
      wrap: FlexWrap.Wrap                 // 自动换行
    }) {
      ForEach([1, 2, 3, 4, 5, 6], (item: number) => {
        Text(`标签 ${item}`)
          .padding(10)
          .backgroundColor('#F0F0F0')
          .margin(5)
      })
    }
    .width('100%')
    .padding(10)
  }
}

基础组件

Text(文本)

Text('显示文本')
  .fontSize(16)                           // 字号,单位 vp
  .fontWeight(FontWeight.Medium)          // 字重:Bold/Medium/Regular/Light
  .fontColor('#333333')                   // 字体颜色(支持十六进制)
  .fontStyle(FontStyle.Italic)            // 字体样式:Normal/Italic
  .textAlign(TextAlign.Center)            // 文本对齐:Start/Center/End
  .maxLines(2)                           // 最大行数,超出省略
  .textOverflow({ overflow: TextOverflow.Ellipsis }) // 溢出处理
  .decoration({ type: TextDecorationType.Underline, color: '#000' }) // 下划线
  .letterSpacing(2)                       // 字间距

// 富文本
Text('第一部分 ') +
Text('高亮文字').fontColor('#FF5500') +
Text(' 第二部分')

Image(图片)

// 本地图片
Image($r('app.media.logo'))               // 使用资源目录引用(推荐)
Image('/common/images/photo.jpg')         // 使用绝对路径

// 网络图片(需要申请网络权限)
Image('https://example.com/photo.jpg')
  .width(200)
  .height(200)
  .borderRadius(10)                       // 圆角
  .alt($r('app.media.placeholder'))       // 加载占位图

// 配置项
Image($r('app.media.photo'))
  .width('50%')                           // 宽度百分比
  .height(150)
  .objectFit(ImageFit.Cover)              // 填充方式:Cover/Contain/Fill/None/Auto
  .copyOption(CopyOptions.None)           // 是否允许长按复制

Button(按钮)

// 文字按钮
Button('确认', { type: ButtonType.Normal, stateEffect: true })
  .fontSize(16)
  .fontColor('#FFFFFF')
  .backgroundColor('#007DFF')
  .borderRadius(8)
  .width(200)
  .height(40)
  .onClick(() => {
    console.info('按钮点击')
  })

// 图标按钮
Button({ type: ButtonType.Circle })
  .width(50)
  .height(50)
  .backgroundColor('#007DFF')
  .onClick(() => { /* ... */ }) {
    Image($r('app.media.icon_add'))
      .width(24)
      .height(24)
  }

// 禁用状态
Button('禁用按钮')
  .enabled(false)                         // 禁用按钮,灰色不可点击

TextInput / TextArea(输入框)

// 单行输入框
TextInput({ placeholder: '请输入用户名', text: this.userName })
  .placeholderFont({ size: 14, weight: FontWeight.Normal })
  .placeholderColor('#999999')
  .caretColor('#007DFF')                  // 光标颜色
  .fontSize(16)
  .maxLength(20)                          // 最大输入长度
  .onChange((value: string) => {
    this.userName = value                  // 实时同步到 @State 变量
  })
  .onSubmit((enterKey: EnterKeyType) => {
    console.info('用户按下了回车键')
  })

// 多行输入框
TextArea({ placeholder: '请输入详细描述', text: this.description })
  .placeholderFont({ size: 14 })
  .fontSize(16)
  .height(120)
  .maxLength(500)
  .showWordLimit(true)                    // 显示字数统计
  .onChange((value: string) => {
    this.description = value
  })

List / ListItem(列表)

// 基础列表
@Entry
@Component
struct ListExample {
  @State products: string[] = ['产品A', '产品B', '产品C', '产品D']

  build() {
    List() {
      ForEach(this.products, (product: string, index: number) => {
        ListItem() {
          Row() {
            Text(`${index + 1}`)
              .fontSize(14)
              .fontColor('#999')
              .width(40)
            Text(product)
              .fontSize(16)
            Blank()
            Text('>')
              .fontColor('#CCC')
          }
          .width('100%')
          .padding(15)
        }
        .onClick(() => {
          console.info(`点击了 ${product}`)
        })
      })
    }
    .width('100%')
    .height('100%')
    .divider({                           // 列表分隔线
      strokeWidth: 0.5,
      color: '#E0E0E0',
      startMargin: 15,
      endMargin: 15
    })
  }
}

Tabs(标签页)

@Entry
@Component
struct TabsExample {
  @State currentIndex: number = 0

  build() {
    Tabs({ barPosition: BarPosition.Start, index: this.currentIndex }) {
      TabContent() {
        Text('首页内容')
          .width('100%')
          .height('100%')
          .backgroundColor('#F5F5F5')
      }
      .tabBar('首页')

      TabContent() {
        Text('分类内容')
      }
      .tabBar('分类')

      TabContent() {
        Text('购物车内容')
      }
      .tabBar('购物车')

      TabContent() {
        Text('我的内容')
      }
      .tabBar('我的')
    }
    .onChange((index: number) => {
      this.currentIndex = index           // 切换时同步状态
    })
  }
}

Dialog(对话框)

// AlertDialog:提示对话框
AlertDialog.show({
  title: '确认删除',
  message: '确定要删除这条记录吗?此操作不可撤销。',
  primaryButton: {
    value: '取消',
    action: () => {
      console.info('用户点击了取消')
    }
  },
  secondaryButton: {
    value: '删除',
    fontColor: '#FF0000',
    action: () => {
      console.info('用户确认删除')
    }
  }
})

// ConfirmDialog:确认对话框(只有一个按钮)
PromptAction.showDialog({
  title: '操作提示',
  message: '处理完成,是否返回列表?',
  buttons: [
    { text: '取消', color: '#999999' },
    { text: '确定', color: '#007DFF' }
  ],
  success: (err, data) => {
    if (data.index === 1) {
      // 用户点击了确定
    }
  }
})

LoadingProgress / Badge

// 加载中
LoadingProgress()
  .width(50)
  .height(50)
  .color('#007DFF')

// 带文字说明的加载
Column() {
  LoadingProgress()
    .width(50)
    .height(50)
  Text('加载中...')
    .fontSize(14)
    .fontColor('#666666')
    .margin({ top: 10 })
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)

// Badge(徽标):显示数字或点
Badge({
  count: 5,                    // 徽标数字
  style: { badgeSize: 16, badgeColor: '#FF0000' },
  position: BadgePosition.RightTop
}) {
  Text('购物车')
}

组件事件

// 常用事件一览
Button('点击')
  .onClick((event: ClickEvent) => {          // 点击事件
    console.info(`点击坐标: (${event.x}, ${event.y})`)
  })

TextInput()
  .onChange((value: string) => {})            // 内容变化
  .onFocus(() => {})                          // 获得焦点
  .onBlur(() => {})                           // 失去焦点
  .onCopy((value: string) => {})              // 复制事件
  .onPaste((value: string) => {})             // 粘贴事件

List()
  .onScrollIndex((startIndex: number, endIndex: number) => {}) // 滚动事件
  .onReachStart(() => {})                     // 到达列表开头
  .onReachEnd(() => {})                      // 到达列表末尾

Swiper()
  .onChange((index: number) => {})            // 轮播切换事件

通用属性(所有组件都支持)

// 尺寸
.width(100)                        // 固定宽度(单位 vp)
.width('100%')                     // 百分比宽度
.height('auto')
.minWidth(50)
.maxWidth(300)

// 背景
.backgroundColor('#F5F5F5')
.backgroundImage($r('app.media.bg'), ImageRepeat.NoRepeat)
.backgroundImageSize(ImageSize.Cover)

// 边框与圆角
.border({ width: 1, color: '#E0E0E0', radius: 8 })

// 内边距
.padding(10)
.padding({ left: 15, right: 15, top: 10, bottom: 10 })

// 外边距
.margin(10)
.margin({ top: 5, bottom: 5 })

// 阴影
.shadow({
  radius: 10,
  color: '#33000000',
  offsetX: 2,
  offsetY: 4
})

// 透明度
.opacity(0.8)

// 可见性
.visibility(Visibility.Hidden)    // Hidden/Visible/None(None 不占位)

页面路由与导航

基于 router 的页面导航

HarmonyOS 提供了 router 模块进行页面间的导航(类似 Web 的 window.location 或 Android 的 Intent)。

// 导入路由模块
import router from '@ohos.router'

// pages/Index.ets 中的跳转代码

// 1. 基本跳转(带参数)
Button('跳转到详情页')
  .onClick(() => {
    router.pushUrl({
      url: 'pages/Detail',           // 目标页面路径(对应 main_pages.json 中的 srcFile)
      params: {                       // 传递给目标页面的参数
        id: 123,
        title: '产品详情'
      }
    })
  })

// 2. 返回上一页
Button('返回')
  .onClick(() => {
    router.back()
  })

// 3. 返回指定页面(可以跳过多级)
Button('返回首页')
  .onClick(() => {
    router.back({ url: 'pages/Index' })
  })

// 4. 替换当前页面(跳转后无法通过 back 返回)
Button('替换当前页')
  .onClick(() => {
    router.replaceUrl({
      url: 'pages/Detail',
      params: { id: 456 }
    })
  })

// 5. 清空栈并跳转(用于登录后的首页跳转)
Button('回到首页')
  .onClick(() => {
    router.clear()
    router.pushUrl({ url: 'pages/Index' })
  })

// 6. 获取路由信息(在目标页面接收参数)
// pages/Detail.ets 中:
import router from '@ohos.router'

@Entry
@Component
struct Detail {
  @State productId: number = 0

  aboutToAppear() {                // 生命周期:组件即将显示
    // 从路由参数中获取传递过来的数据
    const params = router.getParams() as Record<string, number | string>
    this.productId = params.id as number
    console.info(`接收到的商品ID: ${this.productId}`)
  }

  build() {
    Column() {
      Text(`商品ID: ${this.productId}`)
    }
  }
}

基于 NavDestination 的导航(推荐)

HarmonyOS 4.0 起,推荐使用 Navigation + NavPathStack 进行路由管理,相比 router 更加类型安全且支持声明式导航。

// 主页面中定义 Navigation
@Entry
@Component
struct MainPage {
  @Provide('pathStack') pathStack: NavPathStack = new NavPathStack()

  build() {
    Navigation() {
      Column() {
        // 首页内容
        List() {
          ListItem() {
            Text('商品列表项 1')
              .onClick(() => {
                // 推入新页面(导航栈)
                this.pathStack.pushPathByName('Detail', { id: 1 })
              })
          }
        }
      }
      .title('首页')
      .navDestination(this.RouterPageMap)   // 关联路由映射
    }
    .title('首页')
    .mode(NavigationMode.Stack)
  }

  // 路由映射表:定义页面名称与 Builder 的对应关系
  @Builder
  RouterPageMap(name: string, param: object) {
    if (name === 'Detail') {
      DetailPage({ param: param as Record<string, number> })
    } else if (name === 'Settings') {
      SettingsPage()
    }
  }
}

// 子页面中接收参数
@Builder
struct DetailPage {
  @Consume('pathStack') pathStack: NavPathStack
  @State param: Record<string, number> = {}

  aboutToAppear() {
    // 从导航栈获取参数
    const paramObj = this.pathStack.getParamByName('Detail')?.[0] as Record<string, number>
    if (paramObj) {
      this.param = paramObj
    }
  }

  build() {
    NavDestination() {
      Column() {
        Text(`商品ID: ${this.param?.id ?? '未知'}`)
        Button('返回')
          .onClick(() => {
            this.pathStack.pop()      // 弹出当前页面
          })
      }
    }
    .title('商品详情')
    .onBackPressed(() => {
      this.pathStack.pop()           // 处理物理返回键
      return true
    })
  }
}

网络请求

使用 @ohos.net.http 发起请求

// services/HttpService.ets
// 封装通用的网络请求服务

import http from '@ohos.net.http'
import hilog from '../utils/Logger'

// 定义请求配置接口
interface HttpRequestOptions {
  url: string
  method: http.RequestMethod       // 'GET' | 'POST' | 'PUT' | 'DELETE'
  header?: Record<string, string>  // 请求头
  extraData?: string | Object     // 请求体数据
  connectTimeout?: number         // 连接超时(毫秒)
  readTimeout?: number            // 读取超时(毫秒)
}

class HttpService {
  // 默认请求头
  private baseHeaders: Record<string, string> = {
    'Content-Type': 'application/json',
    'Accept': 'application/json'
  }

  // 带 Token 的请求头(需要业务层传入 token)
  private getAuthHeaders(token?: string): Record<string, string> {
    if (token) {
      return {
        ...this.baseHeaders,
        'Authorization': `Bearer ${token}`
      }
    }
    return this.baseHeaders
  }

  // GET 请求
  async get<T>(url: string, token?: string): Promise<T> {
    return this.request<T>({
      url,
      method: http.RequestMethod.GET,
      header: this.getAuthHeaders(token)
    })
  }

  // POST 请求
  async post<T>(url: string, data: Object, token?: string): Promise<T> {
    return this.request<T>({
      url,
      method: http.RequestMethod.POST,
      header: this.getAuthHeaders(token),
      extraData: JSON.stringify(data)
    })
  }

  // 通用请求方法
  private async request<T>(options: HttpRequestOptions): Promise<T> {
    // 创建 HTTP 请求任务
    const client = http.createHttp()

    try {
      const response = await new Promise<http.HttpResponse>((resolve, reject) => {
        client.request(
          options.url,
          {
            method: options.method,
            header: options.header || this.baseHeaders,
            extraData: options.extraData,
            connectTimeout: options.connectTimeout ?? 30000,
            readTimeout: options.readTimeout ?? 30000
          },
          (err, data) => {
            if (err) {
              reject(err)
            } else {
              resolve(data)
            }
          }
        )
      })

      // 解析响应
      if (response.responseCode >= 200 && response.responseCode < 300) {
        // 根据 Content-Type 处理响应
        const result = response.result as string
        return JSON.parse(result) as T
      } else {
        hilog.error(`HTTP错误: ${response.responseCode}`)
        throw new Error(`请求失败: ${response.responseCode}`)
      }
    } finally {
      // 销毁 HTTP 任务,释放资源(必须)
      client.destroy()
    }
  }
}

export default new HttpService()

在页面中调用网络服务

// services/UserService.ets
import HttpService from './HttpService'

interface User {
  id: number
  name: string
  avatar: string
  email: string
}

class UserService {
  // 获取用户信息
  async getUserInfo(userId: number): Promise<User> {
    return await HttpService.get<User>(`https://api.example.com/users/${userId}`)
  }

  // 用户登录
  async login(username: string, password: string): Promise<{ token: string; user: User }> {
    return await HttpService.post<{ token: string; user: User }>(
      'https://api.example.com/auth/login',
      { username, password }
    )
  }

  // 更新用户资料
  async updateProfile(userId: number, data: Partial<User>, token: string): Promise<User> {
    return await HttpService.post<User>(
      `https://api.example.com/users/${userId}`,
      data,
      token
    )
  }
}

export default new UserService()
// pages/UserProfile.ets
// 在页面中使用网络服务

import UserService from '../services/UserService'
import Logger from '../utils/Logger'

@Entry
@Component
struct UserProfile {
  @State isLoading: boolean = false
  @State userInfo: User | null = null
  @State errorMessage: string = ''

  async aboutToAppear() {
    await this.loadUserInfo()
  }

  async loadUserInfo() {
    this.isLoading = true
    this.errorMessage = ''

    try {
      // 模拟获取用户 ID=1 的信息
      this.userInfo = await UserService.getUserInfo(1)
      Logger.info(`用户加载成功: ${this.userInfo.name}`)
    } catch (error) {
      this.errorMessage = '加载失败,请检查网络后重试'
      Logger.error(`加载失败: ${JSON.stringify(error)}`)
    } finally {
      this.isLoading = false
    }
  }

  build() {
    Column() {
      if (this.isLoading) {
        LoadingProgress()
          .width(50)
          .height(50)
      } else if (this.errorMessage) {
        Column() {
          Text(this.errorMessage)
            .fontColor('#FF5500')
          Button('重试')
            .onClick(() => this.loadUserInfo())
        }
      } else if (this.userInfo) {
        Column() {
          Image(this.userInfo.avatar)
            .width(80)
            .height(80)
            .borderRadius(40)

          Text(this.userInfo.name)
            .fontSize(20)
            .fontWeight(FontWeight.Bold)
            .margin({ top: 10 })

          Text(this.userInfo.email)
            .fontColor('#666666')
            .margin({ top: 5 })

          Button('刷新')
            .margin({ top: 20 })
            .onClick(() => this.loadUserInfo())
        }
        .width('100%')
        .padding(20)
      }
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

网络权限配置

在使用网络请求前,需要在 module.json5 中声明网络权限:

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET"     // 允许应用使用网络
      }
    ]
  }
}

本地存储

AppStorage(全局应用存储)

AppStorage 是页面和 Ability 共享的应用级数据存储,适合存储全局配置、用户登录状态等。

// utils/StorageHelper.ets

import AppStorage from '@ohos.app.ability.AppStorage'

class StorageHelper {
  // 存储字符串
  setItem(key: string, value: string): void {
    AppStorage.setOrCreate(key, value)
  }

  // 获取字符串
  getItem(key: string): string | undefined {
    return AppStorage.get<string>(key)
  }

  // 存储布尔值
  setBoolean(key: string, value: boolean): void {
    AppStorage.setOrCreate(key, value)
  }

  // 获取布尔值
  getBoolean(key: string): boolean | undefined {
    return AppStorage.get<boolean>(key)
  }

  // 存储对象(需要序列化)
  setObject<T>(key: string, value: T): void {
    AppStorage.setOrCreate(key, JSON.stringify(value))
  }

  // 获取对象(需要反序列化)
  getObject<T>(key: string): T | undefined {
    const str = AppStorage.get<string>(key)
    if (str) {
      try {
        return JSON.parse(str) as T
      } catch {
        return undefined
      }
    }
    return undefined
  }

  // 删除指定键
  removeItem(key: string): void {
    AppStorage.delete(key)
  }

  // 清空所有存储
  clear(): void {
    AppStorage.clear()
  }

  // 检查键是否存在
  has(key: string): boolean {
    return AppStorage.has(key)
  }
}

export default new StorageHelper()
// 在组件中使用 AppStorage(配合装饰器实现响应式)
@Entry
@Component
struct SettingsPage {
  // @StorageLink 装饰器:与 AppStorage 双向绑定
  // AppStorage 中存的是什么值,this.theme 就是什么值
  // 修改 this.theme,AppStorage 中的值也会同步更新
  @StorageLink('theme') theme: string = 'light'
  @StorageLink('userToken') token: string = ''

  build() {
    Column() {
      Text(`当前主题: ${this.theme}`)
        .fontSize(20)

      Row() {
        Button('浅色主题')
          .onClick(() => {
            this.theme = 'light'
          })

        Button('深色主题')
          .onClick(() => {
            this.theme = 'dark'
          })
      }
      .margin({ top: 20 })

      if (this.token) {
        Text('已登录')
          .fontColor('#52C41A')
          .margin({ top: 20 })

        Button('退出登录')
          .onClick(() => {
            this.token = ''
            AppStorage.delete('userInfo')
          })
      }
    }
    .width('100%')
    .padding(20)
  }
}

首选项(Preferences)

适用于存储少量配置数据,如应用设置项、用户偏好等,数据持久化到本地文件中。

// utils/PreferencesHelper.ets

import dataPreferences from '@ohos.data.preferences'
import hilog from '../utils/Logger'

class PreferencesHelper {
  private preferences: dataPreferences.Preferences | null = null
  private readonly FILE_NAME = 'myapp_preferences'   // 存储文件名

  // 初始化(需要在应用启动时调用一次)
  async init(context: Context): Promise<void> {
    try {
      // 获取 preferences 实例(如果不存在会自动创建)
      this.preferences = await dataPreferences.getPreferences(context, this.FILE_NAME)
      hilog.info('Preferences 初始化成功')
    } catch (error) {
      hilog.error(`Preferences 初始化失败: ${JSON.stringify(error)}`)
    }
  }

  // 存储字符串
  async putString(key: string, value: string): Promise<void> {
    if (!this.preferences) return
    await this.preferences.put(key, value)
    await this.preferences.flush()                 // 写入磁盘(必须调用)
  }

  // 读取字符串
  async getString(key: string, defaultValue: string = ''): Promise<string> {
    if (!this.preferences) return defaultValue
    return await this.preferences.get(key, defaultValue) as string
  }

  // 存储布尔值
  async putBoolean(key: string, value: boolean): Promise<void> {
    if (!this.preferences) return
    await this.preferences.put(key, value)
    await this.preferences.flush()
  }

  // 读取布尔值
  async getBoolean(key: string, defaultValue: boolean = false): Promise<boolean> {
    if (!this.preferences) return defaultValue
    return await this.preferences.get(key, defaultValue) as boolean
  }

  // 删除指定键
  async remove(key: string): Promise<void> {
    if (!this.preferences) return
    await this.preferences.delete(key)
    await this.preferences.flush()
  }

  // 清空所有数据
  async clear(): Promise<void> {
    if (!this.preferences) return
    await this.preferences.clear()
    await this.preferences.flush()
  }
}

export default new PreferencesHelper()
// Ability 中初始化
// EntryAbility.ets

import PreferencesHelper from '../utils/PreferencesHelper'

onWindowStageCreate(windowStage: window.WindowStage) {
  // 加载主页面
  windowStage.loadContent('pages/Index', (err, data) => {
    // ...
  })

  // 初始化本地存储
  PreferencesHelper.init(getContext(this))
}

生命周期

Ability 生命周期

Ability 是 HarmonyOS 应用的基本组件,每个 Ability 有完整的生命周期:

// EntryAbility.ets

import UIAbility from '@ohos.app.ability.UIAbility'
import window from '@ohos.window'
import PreferencesHelper from '../utils/PreferencesHelper'

export default class EntryAbility extends UIAbility {
  // ========== 生命周期钩子 ==========

  // 1. 创建 Ability 时调用(只调用一次)
  onCreate() {
    hilog.info('EntryAbility: onCreate - 应用启动')
    // 适合做:初始化全局资源、全局变量、订阅事件等
    PreferencesHelper.init(this.context)
  }

  // 2. Ability 销毁时调用(只调用一次)
  onDestroy() {
    hilog.info('EntryAbility: onDestroy - 应用退出')
    // 适合做:释放资源、取消订阅、解绑事件等
  }

  // 3. UIAbility 的窗口创建完成时调用
  onWindowStageCreate(windowStage: window.WindowStage) {
    hilog.info('EntryAbility: onWindowStageCreate - 窗口创建完成')
    // 适合做:加载页面内容、设置页面布局
    // 这是最重要的钩子,通常在这里加载首页
    windowStage.loadContent('pages/Index', (err, data) => {
      if (err.code) {
        hilog.error(`加载页面失败: ${JSON.stringify(err)}`)
        return
      }
      hilog.info('首页加载成功')
    })
  }

  // 4. UIAbility 的窗口销毁时调用
  onWindowStageDestroy() {
    hilog.info('EntryAbility: onWindowStageDestroy - 窗口销毁')
    // 适合做:保存状态、清理页面资源
  }

  // 5. Ability 被切换到前台时调用(可见)
  onForeground() {
    hilog.info('EntryAbility: onForeground - 进入前台')
    // 适合做:刷新数据、恢复音乐播放等
  }

  // 6. Ability 被切换到后台时调用(不可见)
  onBackground() {
    hilog.info('EntryAbility: onBackground - 进入后台')
    // 适合做:保存数据、暂停任务
  }

  // ========== 页面跳转回调 ==========

  // 当其他 Ability 跳转过来时,可通过此方法接收参数
  onNewWant(want, launchParam) {
    hilog.info(`收到新的 Want: ${JSON.stringify(want)}`)
    // want 中的 parameters 包含传递过来的数据
  }
}

页面组件生命周期

// pages/Detail.ets

@Entry
@Component
struct Detail {
  // aboutToAppear 和 onPageShow 的区别:
  // aboutToAppear 是组件即将显示,onPageShow 是页面切换到前台

  // 组件即将显示时调用(可以理解为 React 的 useEffect(() => {}, []))
  aboutToAppear() {
    console.info('Detail 页面即将显示')
    // 适合做:初始化数据、请求网络数据
  }

  // 组件即将销毁时调用
  aboutToDisappear() {
    console.info('Detail 页面即将销毁')
    // 适合做:停止定时器、取消网络请求
  }

  // 页面显示(每次页面可见时触发)
  onPageShow() {
    console.info('Detail 页面显示了')
  }

  // 页面隐藏(每次页面不可见时触发)
  onPageHide() {
    console.info('Detail 页面隐藏了')
  }

  // 返回按钮处理(控制物理返回键行为)
  onBackPress(): boolean {
    console.info('用户按下了返回键')
    // 返回 true 表示拦截,false 表示不拦截
    return false
  }

  build() {
    Column() {
      Text('详情页')
    }
    .width('100%')
    .height('100%')
  }
}

常用工具与辅助函数

日志工具

// utils/Logger.ets

import hilog from '@ohos.hilog'

// DOMAIN:日志领域标签(范围 0xFF00-0xFFFF)
// TAG:日志标签,用于过滤
const DOMAIN = 0xFF01
const TAG = 'MyApp'

class Logger {
  debug(message: string, ...args: Object[]): void {
    hilog.debug(DOMAIN, TAG, message, args)
  }

  info(message: string, ...args: Object[]): void {
    hilog.info(DOMAIN, TAG, message, args)
  }

  warn(message: string, ...args: Object[]): void {
    hilog.warn(DOMAIN, TAG, message, args)
  }

  error(message: string, ...args: Object[]): void {
    hilog.error(DOMAIN, TAG, message, args)
  }
}

export default new Logger()

常用工具函数

// utils/CommonUtils.ets

// 1. 格式化日期
function formatDate(timestamp: number, format: string = 'YYYY-MM-DD HH:mm:ss'): string {
  const date = new Date(timestamp)
  const year = date.getFullYear()
  const month = String(date.getMonth() + 1).padStart(2, '0')
  const day = String(date.getDate()).padStart(2, '0')
  const hour = String(date.getHours()).padStart(2, '0')
  const minute = String(date.getMinutes()).padStart(2, '0')
  const second = String(date.getSeconds()).padStart(2, '0')

  return format
    .replace('YYYY', String(year))
    .replace('MM', month)
    .replace('DD', day)
    .replace('HH', hour)
    .replace('mm', minute)
    .replace('ss', second)
}

// 使用示例
console.info(formatDate(Date.now()))           // 2026-06-05 10:24:00
console.info(formatDate(Date.now(), 'YYYY-MM-DD')) // 2026-06-05

// 2. 防抖函数
function debounce<T extends Function>(func: T, delay: number): (args: Parameters<T>) => void {
  let timerId: number | null = null
  return (args: Parameters<T>) => {
    if (timerId !== null) {
      clearTimeout(timerId)
    }
    timerId = setTimeout(() => {
      func(args)
      timerId = null
    }, delay)
  }
}

// 3. 节流函数
function throttle<T extends Function>(func: T, interval: number): (args: Parameters<T>) => void {
  let lastTime = 0
  return (args: Parameters<T>) => {
    const now = Date.now()
    if (now - lastTime >= interval) {
      func(args)
      lastTime = now
    }
  }
}

// 4. 判断是否为空(null / undefined / 空字符串 / 空数组 / 空对象)
function isEmpty(value: Object | string | number | null | undefined): boolean {
  if (value === null || value === undefined) return true
  if (typeof value === 'string') return value.trim() === ''
  if (typeof value === 'object') return JSON.stringify(value) === '{}'
  return false
}

// 5. 深拷贝
function deepClone<T>(source: T): T {
  if (source === null || typeof source !== 'object') return source
  const target = (Array.isArray(source) ? [] : {}) as T
  for (const key in source) {
    if (Object.prototype.hasOwnProperty.call(source, key)) {
      (target as Record<string, unknown>)[key] = deepClone((source as Record<string, unknown>)[key])
    }
  }
  return target
}

// 6. 生成随机ID
function generateId(): string {
  return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`
}

常用第三方库

HTTP 请求库

# 使用 npm 安装 axios 或其他兼容库(注意:HarmonyOS 对 npm 包有兼容性要求)
# 通过 DevEco Studio 的 Terminal 进入 entry 目录执行
npm install axios
// 使用 axios(如果网络库无法直接使用可尝试)
import axios from 'axios'

// 注意:axios 在 HarmonyOS 环境的兼容性需要实际验证
// 建议优先使用官方 @ohos.net.http

图片加载

// 使用 @ohos.multimedia.media.decodeTool 解码图片
// 或使用第三方图片库如 ImageKnife

// 推荐使用官方推荐的 Image 组件,配合占位图和错误处理
Image(url)
  .width(200)
  .height(200)
  .borderRadius(10)
  .alt($r('app.media.placeholder'))     // 加载中/失败占位图
  .syncLoading(true)                   // 同步加载

JSON 序列化

// 内置 JSON 对象即可满足大部分需求
const obj = { name: 'HarmonyOS', version: 4.0 }
const str = JSON.stringify(obj)        // 对象转字符串
const parsed = JSON.parse(str)         // 字符串转对象

// 注意:BigInt 类型 JSON.stringify 会出错,需要特殊处理
const safeStringify = (obj: object): string => {
  return JSON.stringify(obj, (_, value) =>
    typeof value === 'bigint' ? value.toString() : value
  )
}

开发流程与实战

标准开发流程

# 第一步:创建项目
# DevEco Studio -> File -> New -> New Project -> Empty Ability -> Stage 模型

# 第二步:配置文件
# 修改 module.json5 配置应用名称、图标、权限等

# 第三步:编写页面
# 在 pages/ 下创建 .ets 文件

# 第四步:配置路由
# 在 resources/base/profile/main_pages.json 中注册页面

# 第五步:编写业务逻辑
# services/ 中封装数据请求,views/ 中编写视图

# 第六步:预览与调试
# DevEco Studio 右侧 Previewer 实时预览
# 或使用模拟器/真机调试

# 第七步:签名与打包
# DevEco Studio -> Build -> Build HAP(s)
# 签名配置:File -> Project Structure -> Signing Config

完整功能页面开发示例

下面是一个”商品列表页”的完整开发示例,包含网络请求、列表渲染、下拉刷新、上拉加载更多:

// pages/ProductList.ets
// 完整商品列表页:包含网络请求、状态管理、列表渲染、刷新与分页

import ProductService from '../services/ProductService'
import Logger from '../utils/Logger'
import type { Product } from '../models/ProductModel'

// 商品数据模型
// models/ProductModel.ets
export interface Product {
  id: number
  name: string
  price: number
  image: string
  description: string
  stock: number
}

// 产品服务
// services/ProductService.ets
class ProductService {
  private baseUrl = 'https://api.example.com'

  async getProducts(page: number, pageSize: number = 10): Promise<Product[]> {
    // 这里简化处理,实际项目中应该走真实接口
    return new Promise(resolve => {
      setTimeout(() => {
        const products: Product[] = []
        for (let i = 0; i < pageSize; i++) {
          const id = (page - 1) * pageSize + i + 1
          products.push({
            id,
            name: `商品 ${id}`,
            price: Math.floor(Math.random() * 1000) + 10,
            image: `https://picsum.photos/200?random=${id}`,
            description: `这是商品 ${id} 的详细描述`,
            stock: Math.floor(Math.random() * 100)
          })
        }
        resolve(products)
      }, 1000)
    })
  }
}
export default new ProductService()

// ========== 商品列表页面 ==========

@Entry
@Component
struct ProductList {
  @State products: Product[] = []       // 商品列表数据
  @State isLoading: boolean = false     // 是否正在加载
  @State isRefreshing: boolean = false  // 是否正在下拉刷新
  @State hasMore: boolean = true        // 是否还有更多数据
  @State currentPage: number = 1        // 当前页码
  private readonly PAGE_SIZE = 10       // 每页数量
  private scroller: Scroller = new Scroller() // 列表滚动控制器

  // 组件即将显示时加载数据
  aboutToAppear() {
    this.loadProducts()
  }

  // 加载商品列表(首次加载)
  async loadProducts() {
    if (this.isLoading) return
    this.isLoading = true
    this.currentPage = 1

    try {
      const data = await ProductService.getProducts(this.currentPage, this.PAGE_SIZE)
      this.products = data
      this.hasMore = data.length >= this.PAGE_SIZE
      Logger.info(`加载成功,共 ${data.length} 条数据`)
    } catch (error) {
      Logger.error(`加载失败: ${JSON.stringify(error)}`)
    } finally {
      this.isLoading = false
    }
  }

  // 下拉刷新
  async onRefresh() {
    this.isRefreshing = true
    this.currentPage = 1
    this.hasMore = true

    try {
      const data = await ProductService.getProducts(1, this.PAGE_SIZE)
      this.products = data
      this.hasMore = data.length >= this.PAGE_SIZE
    } catch (error) {
      Logger.error(`刷新失败: ${JSON.stringify(error)}`)
    } finally {
      this.isRefreshing = false
    }
  }

  // 上拉加载更多
  async onLoadMore() {
    if (this.isLoading || !this.hasMore) return
    this.isLoading = true
    this.currentPage++

    try {
      const data = await ProductService.getProducts(this.currentPage, this.PAGE_SIZE)
      this.products.push(...data)       // 追加到列表末尾
      this.hasMore = data.length >= this.PAGE_SIZE
      Logger.info(`加载更多成功,当前共 ${this.products.length} 条数据`)
    } catch (error) {
      this.currentPage--                // 加载失败回退页码
      Logger.error(`加载更多失败: ${JSON.stringify(error)}`)
    } finally {
      this.isLoading = false
    }
  }

  // 跳转到商品详情
  goToDetail(product: Product) {
    Logger.info(`点击商品: ${product.name}`)
    // 使用 router 跳转并传递参数
    router.pushUrl({
      url: 'pages/ProductDetail',
      params: { id: product.id, name: product.name }
    })
  }

  build() {
    Column() {
      // 顶部标题栏
      Row() {
        Text('商品列表')
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
      }
      .width('100%')
      .height(56)
      .padding({ left: 16 })
      .backgroundColor('#FFFFFF')
      .shadow({ radius: 4, color: '#11000000', offsetY: 2 })

      // 商品列表
      Refresh({ refreshing: this.isRefreshing, builder: this.refreshBuilder }) {
        List({ scroller: this.scroller }) {
          ForEach(this.products, (product: Product) => {
            ListItem() {
              // 商品卡片
              this.ProductCard(product)
            }
            .onClick(() => this.goToDetail(product))
          })

          // 加载更多footer
          if (this.hasMore) {
            ListItem() {
              Row() {
                if (this.isLoading && this.products.length > 0) {
                  LoadingProgress()
                    .width(24)
                    .height(24)
                  Text('加载中...')
                    .fontSize(14)
                    .fontColor('#999999')
                    .margin({ left: 8 })
                } else {
                  Text('上拉加载更多')
                    .fontSize(14)
                    .fontColor('#666666')
                    .onClick(() => this.onLoadMore())
                }
              }
              .width('100%')
              .height(50)
              .justifyContent(FlexAlign.Center)
            }
          } else {
            ListItem() {
              Text('- 没有更多了 -')
                .fontSize(14)
                .fontColor('#999999')
                .width('100%')
                .textAlign(TextAlign.Center)
                .height(50)
            }
          }
        }
        .width('100%')
        .layoutWeight(1)                 // 占据剩余高度
        .divider({ strokeWidth: 0.5, color: '#F0F0F0' })
        .onReachEnd(() => {              // 到达列表底部时触发加载更多
          this.onLoadMore()
        })
      }
      .onRefreshing(() => {             // 下拉刷新回调
        this.onRefresh()
      })

      // 加载中遮罩(首次加载时显示)
      if (this.isLoading && this.products.length === 0) {
        Column() {
          LoadingProgress()
            .width(50)
            .height(50)
          Text('加载中...')
            .fontSize(14)
            .fontColor('#999999')
            .margin({ top: 10 })
        }
        .width('100%')
        .height('100%')
        .justifyContent(FlexAlign.Center)
        .alignItems(HorizontalAlign.Center)
        .position({ x: 0, y: 0 })
        .backgroundColor('#F5F5F5')
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }

  // 商品卡片组件(子组件抽取,提高代码复用性)
  @Builder
  ProductCard(product: Product) {
    Row({ space: 12 }) {
      // 商品图片
      Image(product.image)
        .width(100)
        .height(100)
        .borderRadius(8)
        .alt($r('app.media.placeholder'))

      // 商品信息
      Column() {
        Text(product.name)
          .fontSize(16)
          .fontWeight(FontWeight.Medium)
          .maxLines(2)
          .textOverflow({ overflow: TextOverflow.Ellipsis })

        Text(product.description)
          .fontSize(12)
          .fontColor('#999999')
          .maxLines(1)
          .textOverflow({ overflow: TextOverflow.Ellipsis })
          .margin({ top: 4 })

        Row() {
          Text(`¥${product.price}`)
            .fontSize(18)
            .fontColor('#FF5500')
            .fontWeight(FontWeight.Bold)

          Blank()

          Text(`库存: ${product.stock}`)
            .fontSize(12)
            .fontColor('#666666')
        }
        .width('100%')
        .margin({ top: 8 })
      }
      .layoutWeight(1)
      .height(100)
      .alignItems(HorizontalAlign.Start)
      .justifyContent(FlexAlign.Start)
    }
    .width('100%')
    .padding(12)
    .backgroundColor('#FFFFFF')
  }

  // 刷新构建器
  @Builder
  refreshBuilder() {
    Row() {
      LoadingProgress()
        .width(24)
        .height(24)
      Text('正在刷新...')
        .fontSize(14)
        .fontColor('#666666')
        .margin({ left: 8 })
    }
    .width('100%')
    .height(50)
    .justifyContent(FlexAlign.Center)
  }
}

常见问题与解决方案

状态变量不更新 UI

// 问题:修改了 @State 变量,但 UI 没有更新

// 原因1:直接修改了对象引用,而没有修改对象内容
@State user: User = { name: '张三', age: 25 }
build() {
  Text(this.user.name)   // 假设修改了 this.user.name,UI 可能不响应
}

// 解决方案:创建新的对象引用(触发响应式更新)
Button('改名')
  .onClick(() => {
    // 错误:直接修改属性
    // this.user.name = '李四'

    // 正确:创建新对象(确保引用变化)
    this.user = { name: '李四', age: this.user.age }
  })

// 原因2:数组操作时使用了不触发响应式的方法
@State list: string[] = []
build() {
  // 错误:直接 push 不一定触发更新(取决于具体版本)
  this.list.push('new item')

  // 正确:替换整个数组
  this.list = [...this.list, 'new item']

  // 或者:使用 ES6 的扩展运算符
  this.list.push('new item')
  this.list = this.list.slice()   // 强制触发更新
}

列表性能优化

// 问题:列表项数量多时性能下降

// 解决方案1:使用 LazyForEach 替代 ForEach
// LazyForEach 按需创建列表项,大幅减少内存占用
LazyForEach(this.products, (product: Product) => {
  ListItem() {
    // ...
  }
}, (product: Product) => JSON.stringify(product.id))  // 必须提供唯一 key

// 解决方案2:合理使用 @State 和 @ObjectLink
// 大型列表使用 @ObjectLink 替代 @State,减少数据复制开销

避免内存泄漏

// 问题:在 aboutToAppear 中发起了异步请求,但组件销毁后回调仍执行

// 解决方案:使用 aboutToDisappear 清理,或使用状态标记
@State isDestroyed: boolean = false

async loadData() {
  this.isDestroyed = false
  const data = await fetchData()
  if (!this.isDestroyed) {     // 检查组件是否已销毁
    this.list = data
  }
}

aboutToDisappear() {
  this.isDestroyed = true      // 组件销毁时标记
}

真机调试时无网络权限

// 确认 module.json5 中已声明权限
// 如果声明了仍无网络,检查是否在模拟器/真机上正确配置了网络

附录:常用资源与链接

资源 链接
HarmonyOS 开发者官网 https://developer.huawei.com/consumer/cn/
DevEco Studio 下载 https://developer.huawei.com/consumer/cn/develop/deveco-studio
ArkUI 组件参考 https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkui-ts-overview
API 参考文档 https://developer.huawei.com/consumer/cn/doc/harmonyos-references/
OpenHarmony 官网 https://www.openharmony.cn/
HarmonyOS 示例代码 https://gitee.com/openharmony/applications_app_samples
Stage 模型开发指南 https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/
权限申请文档 https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/

本文档持续更新。如有遗漏或错误,欢迎反馈。