华为鸿蒙(HarmonyOS)开发
华为鸿蒙(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/ |
本文档持续更新。如有遗漏或错误,欢迎反馈。







