嵌入式鸿蒙系统开发速成快速学习第一部份,基础组件

鸿蒙 ArkTS 快速学习文档

适用版本:HarmonyOS NEXT / API 12+
开发语言:ArkTS(TypeScript 超集)
IDE:DevEco Studio
本文档定位:跟着念、跟着敲、直接上课用

目录


一、开发环境快速搭建

1.1 安装 DevEco Studio

  1. 安装 Node.js(建议 18.x 以上,IDE 自带也可)
  2. 首次启动 → 配置 SDK → 下载 HarmonyOS SDK(API 12)
  3. 创建项目:
    • File → New → Create Project
    • 选择 Empty Ability → 语言选 ArkTS → 设备选 Phone
    • 包名格式:com.example.myapplication

1.2 工程目录结构

plain
 
MyApplication/
├── entry/src/main/ets/          // 源码目录
│   ├── entryability/            // Ability(页面入口)
│   │   └── EntryAbility.ets
│   ├── pages/                   // 页面目录
│   │   └── Index.ets            // 首页
│   └── entrybackupability/      // 备份恢复 Ability
├── entry/src/main/resources/    // 资源文件
│   ├── base/element/            // 颜色、字符串等常量
│   ├── base/media/              // 图片资源
│   └── rawfile/                 // 原始文件(json、txt等)
├── entry/src/main/module.json5  // 模块配置
└── build-profile.json5          // 编译配置


常用插件开发用

DevEco Studio 常用插件清单

鸿蒙官方

表格
插件名一句话说明
HarmonyOS Component Center 官方组件/模板市场,IDE 内直接下载鸿蒙组件
DevEco CodeGenie 华为官方 AI 辅助编程,代码补全与生成
DevEco Device Connector 真机/模拟器连接调试管理
HMS Core Kit 集成华为移动服务(地图、支付、推送等)

代码效率

表格
插件名一句话说明
Rainbow Brackets 彩虹括号,区分嵌套层级
CodeGlance 编辑器右侧代码缩略图,大文件快速定位
Indent Rainbow 彩虹缩进,不同作用域显示不同颜色
Code Spell Checker 实时检查代码拼写错误
ESLint JS/ArkTS 代码规范检查

版本控制

表格
插件名一句话说明
Git Integration 内置图形化 Git 操作
GitLens 行内显示代码作者、修改时间、提交信息

UI / 资源

表格
插件名一句话说明
Color Designer 调色板工具,一键生成 ArkTS 颜色代码
Image Assistant 图片资源管理辅助
Localization Generator 多语言资源快速生成

文档 / 辅助

表格
插件名一句话说明
Markdown Support Markdown 编辑与实时预览
JSON Viewer JSON 格式化、美化、节点折叠
Statistic 代码统计(行数、文件数)



1.3 第一个页面(Hello Harmony)

TypeScript
 
// entry/src/main/ets/pages/Index.ets
@Entry              // 标记为页面入口
@Component          // 标记为自定义组件
struct Index {
  @State message: string = 'Hello HarmonyOS'

  build() {
    Column() {      // 垂直排列容器
      Text(this.message)
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .fontColor('#FF6600')

      Button('点击我')
        .width(200)
        .height(50)
        .backgroundColor('#007DFF')
        .onClick(() => {
          this.message = '鸿蒙,来了!'
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}
核心概念三件套
  • @Entry:页面入口,每个页面必须有且仅有一个
  • @Component:自定义组件装饰器
  • struct:ArkTS 中定义组件的结构体关键字
  • @State:组件内部状态,数据变化自动刷新 UI

二、ArkTS 基础语法速览

2.1 数据类型与变量声明

TypeScript
 
// 基本类型
let userName: string = '张三'
let age: number = 25
let isVip: boolean = true
let score: number | string = 100   // 联合类型:可以是 number 或 string
let anything: any = '任意类型'

// 常量
const PI: number = 3.14159
const APP_NAME: string = '鸿蒙商城'

// 类型推断(可省略类型注解)
let price = 99.9        // 自动推断为 number
let title = '商品标题'   // 自动推断为 string

2.2 数组与函数

TypeScript
 
// 数组声明
let colors: string[] = ['红', '绿', '蓝']
let scores: Array<number> = [85, 90, 78]

// 数组常用操作
colors.push('黄')           // 添加元素
let first = colors[0]      // 读取:'红'
let len = colors.length    // 长度:4
let sliced = colors.slice(0, 2)  // 截取:['红','绿']

// 函数声明(三种写法)
// 写法1:标准函数
function add(a: number, b: number): number {
  return a + b
}

// 写法2:箭头函数(推荐,尤其在回调中)
const multiply = (a: number, b: number): number => {
  return a * b
}

// 写法3:匿名函数(常用于事件回调)
button.onClick(() => {
  console.info('按钮被点击')
})

// 可选参数与默认值
function greet(name: string, greeting?: string): string {
  return `${greeting || '你好'},${name}`
}

// 剩余参数
function sum(...numbers: number[]): number {
  return numbers.reduce((acc, cur) => acc + cur, 0)
}

2.3 对象、接口与枚举

TypeScript
 
// 接口定义(描述对象结构)
interface Product {
  id: number
  name: string
  price: number
  isOnSale?: boolean    // 可选属性
}

// 对象创建
let phone: Product = {
  id: 1001,
  name: 'Mate 70 Pro',
  price: 6999,
  isOnSale: true
}

// 枚举(Enum)—— 用于定义一组命名常量
enum OrderStatus {
  PENDING = '待支付',
  PAID = '已支付',
  SHIPPED = '已发货',
  COMPLETED = '已完成'
}

let currentStatus: OrderStatus = OrderStatus.PAID
console.info(currentStatus)   // 输出:已支付

// 枚举在 UI 中的典型用法
enum ThemeColor {
  PRIMARY = '#007DFF',
  SUCCESS = '#00B578',
  WARNING = '#FF9500',
  DANGER = '#FA2C2C'
}

2.4 联合类型与类型守卫

TypeScript
 
// 联合类型:一个变量可以是多种类型之一
let value: string | number = 'hello'
value = 100   // 合法

// 类型守卫:运行时判断具体类型
function processValue(val: string | number) {
  if (typeof val === 'string') {
    console.info('字符串长度:', val.length)
  } else {
    console.info('数字平方:', val * val)
  }
}

三、常用组件与属性详解

3.1 文本组件 Text

TypeScript
 
Text('普通文本')
  .fontSize(16)                    // 字体大小
  .fontColor('#333333')            // 字体颜色
  .fontWeight(FontWeight.Bold)    // 字重:Lighter/Normal/Bold/Bolder
  .fontFamily('HarmonyOS Sans')   // 字体
  .lineHeight(24)                  // 行高
  .maxLines(2)                    // 最大行数
  .textOverflow({ overflow: TextOverflow.Ellipsis })  // 超出省略
  .textAlign(TextAlign.Center)    // 对齐:Start/Center/End
  .decoration({ type: TextDecorationType.Underline, color: Color.Red })  // 下划线

3.2 按钮组件 Button

TypeScript
 
Button('确认提交', { type: ButtonType.Capsule })   // 胶囊按钮
  .width('80%')
  .height(48)
  .fontSize(16)
  .fontColor(Color.White)
  .backgroundColor('#007DFF')
  .stateEffect(true)              // 点击态效果
  .onClick(() => {
    console.info('提交按钮点击')
  })

// 带图标的按钮
Button() {
  Row() {
    Image($r('app.media.icon_search')).width(20).height(20)
    Text('搜索').fontSize(14).margin({ left: 8 })
  }
}
.width(120)
.height(40)
.backgroundColor('#F5F5F5')

3.3 图片组件 Image

TypeScript
 
// 加载本地资源(resources/base/media/ 下的图片)
Image($r('app.media.logo'))
  .width(100)
  .height(100)
  .borderRadius(50)               // 圆角 → 圆形
  .objectFit(ImageFit.Cover)      // 填充模式:Contain/Cover/Fill/None/ScaleDown
  .interpolation(ImageInterpolation.High)  // 插值质量

// 加载网络图片(需配置网络权限)
Image('https://example.com/avatar.png')
  .width(120)
  .height(120)
  .alt($r('app.media.placeholder'))  // 占位图

// 加载像素图(PixelMap,常用于相机、截图)
Image(pixelMap)
  .width('100%')
  .height(200)

3.4 输入框 TextInput / TextArea

TypeScript
 
@State username: string = ''
@State password: string = ''

TextInput({ placeholder: '请输入用户名', text: $$this.username })
  .width('90%')
  .height(48)
  .backgroundColor('#F5F5F5')
  .borderRadius(8)
  .padding({ left: 16, right: 16 })
  .fontSize(14)
  .maxLength(20)                  // 最大长度
  .inputFilter('[a-zA-Z0-9]', (value: string) => {
    // 输入过滤:只允许字母数字
  })

TextInput({ placeholder: '请输入密码', text: $$this.password })
  .type(InputType.Password)      // 密码输入模式
  .showPasswordIcon(true)        // 显示密码可见切换图标
  .width('90%')
  .height(48)

// 多行文本输入
TextArea({ placeholder: '请输入描述', text: $$this.description })
  .width('90%')
  .height(120)
  .backgroundColor('#F8F8F8')

3.5 开关与选择器

TypeScript
 
@State isOn: boolean = false
@State selectedIndex: number = 0

// Toggle 开关
Toggle({ type: ToggleType.Switch, isOn: $$this.isOn })
  .selectedColor('#007DFF')
  .onChange((isOn: boolean) => {
    console.info('开关状态:', isOn)
  })

// Checkbox 多选框
Checkbox()
  .select(this.isChecked)
  .selectedColor('#007DFF')
  .onChange((value: boolean) => {
    this.isChecked = value
  })

// Slider 滑块
Slider({ value: 50, min: 0, max: 100, step: 1 })
  .width('80%')
  .blockColor('#007DFF')
  .trackColor('#E5E5E5')
  .selectedColor('#007DFF')
  .onChange((value: number, mode: SliderChangeMode) => {
    console.info('当前值:', value)
  })

// Radio 单选
Radio({ value: 'male', group: 'gender' })
  .checked(this.gender === 'male')
  .onChange(() => { this.gender = 'male' })

3.6 进度条与加载

TypeScript
 
// 进度条
Progress({ value: 60, total: 100, type: ProgressType.Linear })
  .width('80%')
  .height(8)
  .color('#007DFF')
  .backgroundColor('#E5E5E5')

// 环形进度条
Progress({ value: 75, total: 100, type: ProgressType.Ring })
  .width(80)
  .height(80)
  .color('#007DFF')

// Loading 加载动画
LoadingProgress()
  .width(40)
  .height(40)
  .color('#007DFF')

3.7 列表 List / ListItem

TypeScript
 
@State productList: Product[] = [
  { id: 1, name: '手机', price: 3999 },
  { id: 2, name: '平板', price: 2999 },
  { id: 3, name: '手表', price: 1999 }
]

List({ space: 12 }) {            // 列表项间距
  ForEach(this.productList, (item: Product, index: number) => {
    ListItem() {
      Row() {
        Column() {
          Text(item.name)
            .fontSize(16)
            .fontWeight(FontWeight.Medium)
          Text(`¥${item.price}`)
            .fontSize(14)
            .fontColor('#FF6600')
        }
        .alignItems(HorizontalAlign.Start)
        .layoutWeight(1)

        Button('购买')
          .width(80)
          .height(36)
          .fontSize(14)
          .backgroundColor('#007DFF')
      }
      .width('100%')
      .padding(16)
      .backgroundColor(Color.White)
      .borderRadius(12)
    }
    .swipeAction({ end: this.DeleteBuilder(item) })  // 侧滑删除
  }, (item: Product) => item.id.toString())          // 键值函数,优化渲染
}
.width('100%')
.height('100%')
.padding(16)
.backgroundColor('#F5F5F5')
.listDirection(Axis.Vertical)     // 排列方向
.divider({ strokeWidth: 1, color: '#EEEEEE' })  // 分割线
.edgeEffect(EdgeEffect.Spring)    // 边缘弹性效果
.scrollBar(BarState.Auto)         // 滚动条

// 侧滑删除按钮构建器
@Builder
DeleteBuilder(item: Product) {
  Button() {
    Image($r('app.media.icon_delete')).width(24).height(24).fillColor(Color.White)
  }
  .width(60)
  .height('100%')
  .backgroundColor('#FA2C2C')
  .onClick(() => {
    this.productList = this.productList.filter(p => p.id !== item.id)
  })
}

3.8 滚动容器 Scroll

TypeScript
 
Scroll() {
  Column() {
    // 大量内容...
    ForEach(this.dataList, (item: string) => {
      Text(item)
        .width('100%')
        .height(60)
        .backgroundColor('#F8F8F8')
        .margin({ bottom: 8 })
    })
  }
  .width('100%')
}
.width('100%')
.height('100%')
.scrollable(ScrollDirection.Vertical)   // 垂直滚动
.scrollBar(BarState.On)                 // 始终显示滚动条
.edgeEffect(EdgeEffect.Spring)           // 弹性边缘
.onScroll((xOffset: number, yOffset: number) => {
  console.info('滚动偏移:', yOffset)
})

3.9 标签页 Tabs

TypeScript
 
@State currentIndex: number = 0

Tabs({ barPosition: BarPosition.End }) {   // 底部导航栏
  TabContent() {
    HomePage()    // 首页内容
  }
  .tabBar(this.TabBuilder('首页', 0, $r('app.media.icon_home')))

  TabContent() {
    CategoryPage()  // 分类内容
  }
  .tabBar(this.TabBuilder('分类', 1, $r('app.media.icon_category')))

  TabContent() {
    CartPage()    // 购物车内容
  }
  .tabBar(this.TabBuilder('购物车', 2, $r('app.media.icon_cart')))

  TabContent() {
    MinePage()    // 我的内容
  }
  .tabBar(this.TabBuilder('我的', 3, $r('app.media.icon_mine')))
}
.width('100%')
.height('100%')
.barWidth('100%')
.barHeight(56)
.onChange((index: number) => {
  this.currentIndex = index
})

// 自定义 TabBar 构建器
@Builder
TabBuilder(title: string, targetIndex: number, icon: Resource) {
  Column() {
    Image(icon)
      .width(24)
      .height(24)
      .fillColor(this.currentIndex === targetIndex ? '#007DFF' : '#999999')
    Text(title)
      .fontSize(12)
      .fontColor(this.currentIndex === targetIndex ? '#007DFF' : '#999999')
      .margin({ top: 4 })
  }
  .width('100%')
  .height('100%')
  .justifyContent(FlexAlign.Center)
}

3.10 轮播图 Swiper

TypeScript
 
@State bannerList: string[] = [
  'https://example.com/banner1.jpg',
  'https://example.com/banner2.jpg',
  'https://example.com/banner3.jpg'
]

Swiper() {
  ForEach(this.bannerList, (url: string, index: number) => {
    Image(url)
      .width('100%')
      .height(180)
      .objectFit(ImageFit.Cover)
      .borderRadius(12)
  })
}
.width('100%')
.height(180)
.indicator(true)                    // 显示指示器
.autoPlay(true)                     // 自动播放
.interval(3000)                     // 轮播间隔 3 秒
.loop(true)                         // 循环播放
.duration(500)                     // 切换动画时长
.itemSpace(12)                      // 项间距
.onChange((index: number) => {
  console.info('当前轮播索引:', index)
})

3.11 徽章 Badge

TypeScript
 
Badge({
  value: '99+',                    // 徽章内容
  position: BadgePosition.RightTop,
  style: { badgeSize: 16, badgeColor: '#FA2C2C' }
}) {
  Image($r('app.media.icon_message'))
    .width(32)
    .height(32)
}

// 小红点模式
Badge({
  value: '',
  position: BadgePosition.RightTop,
  style: { badgeSize: 8, badgeColor: '#FA2C2C' }
}) {
  Text('消息')
    .fontSize(14)
}

四、布局系统(基础 → 高阶)

4.1 线性布局 Column / Row

TypeScript
 
// Column:垂直排列(主轴 Y,交叉轴 X)
Column({ space: 16 }) {            // 子组件间距 16
  Text('标题').fontSize(20).fontWeight(FontWeight.Bold)
  Text('内容1').fontSize(14)
  Text('内容2').fontSize(14)
}
.width('100%')
.height('100%')
.alignItems(HorizontalAlign.Center)    // 交叉轴对齐:Start/Center/End
.justifyContent(FlexAlign.Center)       // 主轴对齐
.padding(20)
.backgroundColor('#F5F5F5')

// Row:水平排列(主轴 X,交叉轴 Y)
Row({ space: 12 }) {
  Image($r('app.media.avatar')).width(48).height(48).borderRadius(24)
  Column() {
    Text('用户名').fontSize(16).fontWeight(FontWeight.Medium)
    Text('在线').fontSize(12).fontColor('#999999')
  }
  .alignItems(HorizontalAlign.Start)
  .layoutWeight(1)                    // 占据剩余空间

  Button('关注')
    .width(80)
    .height(36)
    .fontSize(14)
    .backgroundColor('#007DFF')
}
.width('100%')
.height(72)
.padding({ left: 16, right: 16 })
.backgroundColor(Color.White)

4.2 弹性布局 Flex

TypeScript
 
Flex({
  direction: FlexDirection.Row,       // 方向:Row/RowReverse/Column/ColumnReverse
  wrap: FlexWrap.Wrap,                // 换行:NoWrap/Wrap/WrapReverse
  justifyContent: FlexAlign.SpaceBetween,  // 主轴对齐
  alignItems: ItemAlign.Center        // 交叉轴对齐
}) {
  ForEach(this.tagList, (tag: string) => {
    Text(tag)
      .fontSize(14)
      .padding({ top: 6, bottom: 6, left: 12, right: 12 })
      .backgroundColor('#F0F0F0')
      .borderRadius(16)
      .margin({ bottom: 8, right: 8 })
  })
}
.width('100%')
.padding(16)

4.3 层叠布局 Stack / Position

TypeScript
 
Stack({ alignContent: Alignment.BottomEnd }) {
  Image($r('app.media.cover'))
    .width('100%')
    .height(200)
    .objectFit(ImageFit.Cover)
    .borderRadius(12)

  Text('VIP')
    .fontSize(12)
    .fontColor(Color.White)
    .backgroundColor('#FF9500')
    .padding({ top: 4, bottom: 4, left: 8, right: 8 })
    .borderRadius({ topLeft: 12, bottomRight: 12 })
    .position({ x: 0, y: 0 })        // 绝对定位
}
.width('100%')
.height(200)

// 使用 Position 绝对定位
Column() {
  Text('底层内容').width('100%').height('100%')
  Button('悬浮按钮')
    .width(56)
    .height(56)
    .backgroundColor('#007DFF')
    .position({ x: '80%', y: '80%' })   // 百分比定位
}
.width('100%')
.height('100%')

4.4 网格布局 Grid

TypeScript
 
Grid() {
  ForEach(this.productList, (item: Product) => {
    GridItem() {
      Column() {
        Image(item.image)
          .width('100%')
          .aspectRatio(1)              // 保持 1:1 比例
          .objectFit(ImageFit.Cover)
          .borderRadius(8)
        Text(item.name)
          .fontSize(14)
          .maxLines(1)
          .textOverflow({ overflow: TextOverflow.Ellipsis })
          .margin({ top: 8 })
        Text(`¥${item.price}`)
          .fontSize(16)
          .fontColor('#FF6600')
          .fontWeight(FontWeight.Bold)
      }
      .width('100%')
      .padding(8)
      .backgroundColor(Color.White)
      .borderRadius(12)
    }
  })
}
.width('100%')
.columnsTemplate('1fr 1fr')           // 两列等宽
.rowsGap(12)                         // 行间距
columnsGap(12)                      // 列间距
.padding(16)
.backgroundColor('#F5F5F5')

4.5 相对布局 RelativeContainer

TypeScript
 
RelativeContainer() {
  Text('左上角')
    .id('topLeft')
    .fontSize(14)
    .alignRules({
      top: { anchor: '__container__', align: VerticalAlign.Top },
      left: { anchor: '__container__', align: HorizontalAlign.Start }
    })

  Text('右上角')
    .id('topRight')
    .fontSize(14)
    .alignRules({
      top: { anchor: '__container__', align: VerticalAlign.Top },
      right: { anchor: '__container__', align: HorizontalAlign.End }
    })

  Text('居中')
    .id('center')
    .fontSize(16)
    .fontWeight(FontWeight.Bold)
    .alignRules({
      center: { anchor: '__container__', align: VerticalAlign.Center },
      middle: { anchor: '__container__', align: HorizontalAlign.Center }
    })

  Text('跟随左上角下方')
    .id('below')
    .fontSize(14)
    .alignRules({
      top: { anchor: 'topLeft', align: VerticalAlign.Bottom },
      left: { anchor: 'topLeft', align: HorizontalAlign.Start }
    })
    .margin({ top: 8 })
}
.width('100%')
.height(300)
.backgroundColor('#F8F8F8')

五、样式系统与复用机制

5.1 通用属性(所有组件可用)

TypeScript
 
// 尺寸
.width(100) / .width('50%') / .width('100%')
.height(48)
.size({ width: 100, height: 48 })   // 同时设置宽高
.aspectRatio(1)                     // 宽高比
.layoutWeight(1)                   // 权重分配剩余空间

// 位置
.position({ x: 10, y: 20 })         // 绝对定位
.offset({ x: 10, y: 20 })           // 相对偏移
.markAnchor({ x: 0.5, y: 0.5 })    // 锚点

// 背景
.backgroundColor('#FFFFFF')
.backgroundColor(Color.Transparent)
.backgroundImage($r('app.media.bg'))
.backgroundImageSize(ImageSize.Cover)

// 边框
.border({
  width: 1,
  color: '#E5E5E5',
  style: BorderStyle.Solid,         // Solid/Dashed/Dotted
  radius: 12                        // 圆角
})
.borderRadius(12)                   // 简写
.borderWidth(1)
.borderColor('#E5E5E5')

// 阴影(高阶)
.shadow({
  radius: 10,                       // 模糊半径
  color: 'rgba(0,0,0,0.1)',       // 阴影颜色
  offsetX: 0,                       // X 偏移
  offsetY: 4                        // Y 偏移
})

// 内外边距
.padding(16)                        // 四边相同
.padding({ top: 8, bottom: 8, left: 16, right: 16 })
.margin(16)
.margin({ top: 8, bottom: 0, left: 16, right: 16 })

// 可见性与层级
.visibility(Visibility.Visible)      // Visible/Hidden/None
.opacity(0.5)                       // 透明度 0~1
.zIndex(10)                         // 层级

5.2 样式复用 @Styles

TypeScript
 
// 定义可复用的样式函数
@Styles
function cardStyle() {
  .width('100%')
  .padding(16)
  .backgroundColor(Color.White)
  .borderRadius(12)
  .shadow({ radius: 8, color: 'rgba(0,0,0,0.06)', offsetX: 0, offsetY: 2 })
}

@Styles
function primaryButtonStyle() {
  .width('90%')
  .height(48)
  .fontSize(16)
  .fontColor(Color.White)
  .backgroundColor('#007DFF')
  .borderRadius(8)
}

// 使用
Column() {
  Text('卡片标题').fontSize(18).fontWeight(FontWeight.Bold)
  Text('卡片内容').fontSize(14).fontColor('#666666').margin({ top: 8 })
}
.cardStyle()                        // 直接调用
.margin({ bottom: 12 })

Button('立即购买')
  .primaryButtonStyle()

5.3 结构复用 @Builder

TypeScript
 
// 局部 Builder(定义在组件内部)
@Builder
ProductCard(item: Product) {
  Row() {
    Image(item.image)
      .width(80)
      .height(80)
      .borderRadius(8)
      .objectFit(ImageFit.Cover)
    Column() {
      Text(item.name).fontSize(16).fontWeight(FontWeight.Medium)
      Text(`¥${item.price}`).fontSize(18).fontColor('#FF6600').margin({ top: 8 })
    }
    .alignItems(HorizontalAlign.Start)
    .layoutWeight(1)
    .margin({ left: 12 })
  }
  .width('100%')
  .padding(12)
  .backgroundColor(Color.White)
  .borderRadius(12)
}

// 全局 Builder(定义在组件外部,可跨组件复用)
@Builder
export function GlobalProductCard(item: Product) {
  // ... 同上
}

// 使用
List() {
  ForEach(this.productList, (item: Product) => {
    ListItem() {
      this.ProductCard(item)        // 调用局部 Builder
      // 或 GlobalProductCard(item)  // 调用全局 Builder
    }
  })
}

5.4 动画与过渡

TypeScript
 
@State isExpand: boolean = false

Column() {
  Text('点击展开')
    .fontSize(16)
    .onClick(() => {
      this.isExpand = !this.isExpand
    })

  if (this.isExpand) {
    Column() {
      Text('展开的内容1')
      Text('展开的内容2')
      Text('展开的内容3')
    }
    .width('100%')
    .padding(16)
    .backgroundColor('#F8F8F8')
    .transition(TransitionEffect.asymmetric(
      TransitionEffect.OPACITY.combine(TransitionEffect.translate({ y: -20 })),
      TransitionEffect.OPACITY
    ))  // 进入/退出非对称动画
  }
}
.width('100%')

// 属性动画
Text('缩放文字')
  .fontSize(20)
  .scale({ x: this.isExpand ? 1.2 : 1, y: this.isExpand ? 1.2 : 1 })
  .animation({
    duration: 300,
    curve: Curve.EaseInOut,
    iterations: 1,
    playMode: PlayMode.Normal
  })

// 显式动画
Button('播放动画')
  .onClick(() => {
    animateTo({
      duration: 500,
      curve: Curve.Spring,
      onFinish: () => {
        console.info('动画结束')
      }
    }, () => {
      this.offsetY = 100   // 在闭包中修改状态,触发动画
    })
  })

六、页面与组件生命周期

6.1 页面生命周期(Page)

TypeScript
 
import { router } from '@kit.ArkUI'

@Entry
@Component
struct Index {
  @State message: string = ''

  // ========== 页面生命周期 ==========

  // 1. 页面创建时触发(只触发一次)
  aboutToAppear() {
    console.info('【生命周期】aboutToAppear:页面即将出现')
    // 适合:初始化数据、请求首屏数据、注册监听
    this.loadData()
  }

  // 2. 页面显示时触发(每次从后台回到前台都会触发)
  onPageShow() {
    console.info('【生命周期】onPageShow:页面显示')
    // 适合:刷新数据、恢复页面状态
  }

  // 3. 页面隐藏时触发(切换到其他页面或按 Home 键)
  onPageHide() {
    console.info('【生命周期】onPageHide:页面隐藏')
    // 适合:暂停计时器、保存草稿
  }

  // 4. 页面销毁前触发(只触发一次)
  aboutToDisappear() {
    console.info('【生命周期】aboutToDisappear:页面即将销毁')
    // 适合:取消网络请求、清除定时器、解绑监听
  }

  // 5. 返回键拦截(可选)
  onBackPress(): boolean {
    console.info('【生命周期】onBackPress:返回键被按下')
    // return true  表示拦截返回,不退出页面
    // return false 表示不拦截,正常退出
    return false
  }

  async loadData() {
    // 模拟网络请求
    this.message = '数据加载完成'
  }

  build() {
    Column() {
      Text(this.message)
        .fontSize(20)
        .fontWeight(FontWeight.Bold)

      Button('跳转详情页')
        .onClick(() => {
          router.pushUrl({
            url: 'pages/Detail',
            params: { id: 123, name: '商品A' }
          })
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

6.2 自定义组件生命周期(Component)

TypeScript
 
@Component
struct MyComponent {
  @State count: number = 0
  private timerId: number = -1

  // 组件创建时
  aboutToAppear() {
    console.info('组件 aboutToAppear')
    // 启动定时器
    this.timerId = setInterval(() => {
      this.count++
    }, 1000)
  }

  // 组件销毁前
  aboutToDisappear() {
    console.info('组件 aboutToDisappear')
    // 必须清理!防止内存泄漏
    clearInterval(this.timerId)
  }

  // 组件布局完成时(API 12+)
  onDidBuild() {
    console.info('组件 onDidBuild:布局已完成')
  }

  build() {
    Text(`计数:${this.count}`)
      .fontSize(24)
      .fontWeight(FontWeight.Bold)
  }
}

6.3 生命周期完整流程图

plain
 
页面A(Index)
  │
  ├─ aboutToAppear()     ← 初始化数据、请求首屏
  ├─ build()             ← 构建 UI
  ├─ onDidBuild()        ← 布局完成
  ├─ onPageShow()        ← 页面可见
  │
  │   【用户点击跳转】
  │
  ├─ onPageHide()        ← 页面隐藏(还在栈中)
  │
  │   【用户按返回键】
  │
  ├─ onPageShow()        ← 页面重新显示
  │
  │   【用户关闭页面】
  │
  └─ aboutToDisappear()  ← 清理资源、取消请求

七、数据更新与状态管理

7.1 状态装饰器速查表

表格
 
 
装饰器作用范围数据流向使用场景
@State 组件内部 内部可变 组件私有状态(开关、计数)
@Prop 父 → 子 单向同步 父传子,子不可改
@Link 父 ↔ 子 双向同步 父子共享状态(表单输入)
@Provide / @Consume 祖先 → 后代 双向同步 跨层级传值(主题、语言)
@Observed + @ObjectLink 嵌套对象 深度监听 对象数组内部属性变化
@Watch 监听变化 状态变化时执行回调

7.2 @State — 组件内部状态

TypeScript
 
@Entry
@Component
struct CounterPage {
  @State count: number = 0
  @State userName: string = '张三'

  build() {
    Column({ space: 20 }) {
      Text(`当前计数:${this.count}`)
        .fontSize(24)
        .fontWeight(FontWeight.Bold)

      Row({ space: 16 }) {
        Button('-').onClick(() => { this.count-- })
        Button('+').onClick(() => { this.count++ })
      }

      TextInput({ text: $$this.userName, placeholder: '输入名字' })
        .width('80%')
        .height(48)
        .backgroundColor('#F5F5F5')
        .borderRadius(8)
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

7.3 @Prop / @Link — 父子组件通信

TypeScript
 
// 父组件
@Entry
@Component
struct ParentPage {
  @State parentCount: number = 10

  build() {
    Column({ space: 20 }) {
      Text(`父组件:${this.parentCount}`)
        .fontSize(20)
        .fontWeight(FontWeight.Bold)

      // @Prop:单向传递,子组件修改不影响父
      ChildProp({ propCount: this.parentCount })

      // @Link:双向绑定,子组件修改同步到父
      ChildLink({ linkCount: this.parentCount })

      Button('父组件+1')
        .onClick(() => { this.parentCount++ })
    }
    .width('100%')
    .padding(20)
  }
}

// 子组件 — @Prop(单向)
@Component
struct ChildProp {
  @Prop propCount: number   // 从父接收,本地副本

  build() {
    Column() {
      Text(`@Prop 接收:${this.propCount}`)
      Button('子组件+1(不影响父)')
        .onClick(() => { this.propCount++ })   // 只改本地
    }
    .padding(16)
    .backgroundColor('#FFF3E0')
    .borderRadius(8)
  }
}

// 子组件 — @Link(双向)
@Component
struct ChildLink {
  @Link linkCount: number    // 与父共享引用

  build() {
    Column() {
      Text(`@Link 绑定:${this.linkCount}`)
      Button('子组件+1(同步父)')
        .onClick(() => { this.linkCount++ })   // 同步修改父
    }
    .padding(16)
    .backgroundColor('#E3F2FD')
    .borderRadius(8)
  }
}

7.4 @Provide / @Consume — 跨层级传值

TypeScript
 
// 祖先组件提供
@Entry
@Component
struct ThemePage {
  @Provide('themeColor') themeColor: string = '#007DFF'
  @Provide('fontSize') fontSize: number = 16

  build() {
    Column() {
      GrandChild()    // 深层嵌套的后代组件
    }
  }
}

// 后代组件消费(无论嵌套多深)
@Component
struct GrandChild {
  @Consume('themeColor') themeColor: string
  @Consume('fontSize') fontSize: number

  build() {
    Text('深层组件也能拿到主题')
      .fontColor(this.themeColor)
      .fontSize(this.fontSize)
  }
}

7.5 @Observed + @ObjectLink — 对象深度监听

TypeScript
 
// 必须用 @Observed 标记类
@Observed
class Product {
  id: number
  name: string
  price: number
  isFavorite: boolean = false

  constructor(id: number, name: string, price: number) {
    this.id = id
    this.name = name
    this.price = price
  }
}

@Entry
@Component
struct ProductListPage {
  @State productList: Product[] = [
    new Product(1, '手机', 3999),
    new Product(2, '耳机', 899)
  ]

  build() {
    List() {
      ForEach(this.productList, (item: Product) => {
        ListItem() {
          ProductItem({ product: item })   // 传入对象
        }
      })
    }
  }
}

@Component
struct ProductItem {
  @ObjectLink product: Product   // 深度监听对象属性变化

  build() {
    Row() {
      Text(this.product.name)
      Text(`¥${this.product.price}`)
      Button(this.product.isFavorite ? '已收藏' : '收藏')
        .onClick(() => {
          this.product.isFavorite = !this.product.isFavorite  // 触发 UI 刷新
        })
    }
  }
}

7.6 @Watch — 监听状态变化

TypeScript
 
@Entry
@Component
struct WatchDemo {
  @State @Watch('onCountChange') count: number = 0
  @State message: string = '初始状态'

  onCountChange() {
    // count 变化时自动执行
    if (this.count > 10) {
      this.message = '计数超过10了!'
    } else {
      this.message = `当前计数:${this.count}`
    }
    console.info('count 变化为:', this.count)
  }

  build() {
    Column({ space: 16 }) {
      Text(this.message).fontSize(18).fontColor('#FF6600')
      Text(`${this.count}`).fontSize(48).fontWeight(FontWeight.Bold)
      Button('+1').onClick(() => { this.count++ })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

八、网络请求实战

8.1 配置网络权限

entry/src/main/module.json5 中添加:
JSON
 
{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET"
      }
    ]
  }
}

8.2 HTTP 请求封装

TypeScript
 
import { http } from '@kit.NetworkKit'

// 封装网络请求类
class HttpRequest {
  private httpClient: http.HttpRequest

  constructor() {
    this.httpClient = http.createHttp()
  }

  // GET 请求
  async get<T>(url: string, params?: Record<string, string>): Promise<T> {
    let fullUrl = url
    if (params) {
      const query = Object.entries(params)
        .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
        .join('&')
      fullUrl += `?${query}`
    }

    try {
      const response = await this.httpClient.request(fullUrl, {
        method: http.RequestMethod.GET,
        header: { 'Content-Type': 'application/json' },
        connectTimeout: 60000,
        readTimeout: 60000
      })

      if (response.responseCode === 200) {
        return JSON.parse(response.result.toString()) as T
      } else {
        throw new Error(`请求失败,状态码:${response.responseCode}`)
      }
    } catch (error) {
      console.error('GET 请求错误:', JSON.stringify(error))
      throw error
    }
  }

  // POST 请求
  async post<T>(url: string, data: object): Promise<T> {
    try {
      const response = await this.httpClient.request(url, {
        method: http.RequestMethod.POST,
        header: {
          'Content-Type': 'application/json',
          'Authorization': 'Bearer your_token_here'   // 如需 Token
        },
        extraData: JSON.stringify(data),
        connectTimeout: 60000,
        readTimeout: 60000
      })

      if (response.responseCode === 200) {
        return JSON.parse(response.result.toString()) as T
      } else {
        throw new Error(`请求失败,状态码:${response.responseCode}`)
      }
    } catch (error) {
      console.error('POST 请求错误:', JSON.stringify(error))
      throw error
    }
  }

  // 取消请求
  destroy() {
    this.httpClient.destroy()
  }
}

// 导出单例
export const httpRequest = new HttpRequest()

8.3 实战:请求商品列表

TypeScript
 
import { httpRequest } from '../utils/HttpRequest'

// 定义数据接口
interface ApiResponse<T> {
  code: number
  message: string
  data: T
}

interface Product {
  id: number
  name: string
  price: number
  image: string
}

@Entry
@Component
struct NetworkDemoPage {
  @State productList: Product[] = []
  @State isLoading: boolean = false
  @State errorMsg: string = ''

  aboutToAppear() {
    this.fetchProducts()
  }

  aboutToDisappear() {
    httpRequest.destroy()   // 页面销毁时取消请求
  }

  async fetchProducts() {
    this.isLoading = true
    this.errorMsg = ''

    try {
      const res = await httpRequest.get<ApiResponse<Product[]>>(
        'https://api.example.com/products',
        { page: '1', size: '10' }
      )

      if (res.code === 200) {
        this.productList = res.data
      } else {
        this.errorMsg = res.message
      }
    } catch (err) {
      this.errorMsg = '网络请求失败,请检查网络'
    } finally {
      this.isLoading = false
    }
  }

  build() {
    Column() {
      if (this.isLoading) {
        Column() {
          LoadingProgress().width(40).height(40).color('#007DFF')
          Text('加载中...').fontSize(14).fontColor('#999999').margin({ top: 12 })
        }
        .width('100%')
        .height('100%')
        .justifyContent(FlexAlign.Center)
      } else if (this.errorMsg !== '') {
        Column() {
          Text(this.errorMsg).fontSize(16).fontColor('#FA2C2C')
          Button('重新加载')
            .margin({ top: 16 })
            .onClick(() => { this.fetchProducts() })
        }
        .width('100%')
        .height('100%')
        .justifyContent(FlexAlign.Center)
      } else {
        List({ space: 12 }) {
          ForEach(this.productList, (item: Product) => {
            ListItem() {
              Row() {
                Image(item.image)
                  .width(80)
                  .height(80)
                  .borderRadius(8)
                  .objectFit(ImageFit.Cover)
                Column() {
                  Text(item.name).fontSize(16).fontWeight(FontWeight.Medium)
                  Text(`¥${item.price}`).fontSize(18).fontColor('#FF6600').margin({ top: 8 })
                }
                .alignItems(HorizontalAlign.Start)
                .layoutWeight(1)
                .margin({ left: 12 })
              }
              .width('100%')
              .padding(12)
              .backgroundColor(Color.White)
              .borderRadius(12)
            }
          })
        }
        .width('100%')
        .height('100%')
        .padding(16)
        .backgroundColor('#F5F5F5')
        .refresh({
          refreshing: $$this.isLoading,
          builder: this.RefreshBuilder,
          onRefresh: () => { this.fetchProducts() }
        })
      }
    }
    .width('100%')
    .height('100%')
  }

  @Builder
  RefreshBuilder() {
    Row() {
      LoadingProgress().width(24).height(24).color('#007DFF')
      Text('下拉刷新').fontSize(14).fontColor('#999999').margin({ left: 8 })
    }
    .width('100%')
    .height(60)
    .justifyContent(FlexAlign.Center)
  }
}

8.4 上传文件

TypeScript
 
import { request } from '@kit.BasicServicesKit'

async uploadFile(fileUri: string) {
  const uploadConfig: request.UploadConfig = {
    url: 'https://api.example.com/upload',
    header: { 'Authorization': 'Bearer token' },
    method: 'POST',
    files: [{ filename: 'image.jpg', name: 'file', uri: fileUri, type: 'image/jpeg' }],
    data: [{ name: 'description', value: '头像上传' }]
  }

  try {
    const uploadTask = await request.uploadFile(getContext(), uploadConfig)
    uploadTask.on('complete', (taskStates) => {
      console.info('上传完成:', JSON.stringify(taskStates))
    })
    uploadTask.on('fail', (taskStates) => {
      console.error('上传失败:', JSON.stringify(taskStates))
    })
  } catch (err) {
    console.error('上传异常:', JSON.stringify(err))
  }
}

8.5 下载文件

TypeScript
 
import { request } from '@kit.BasicServicesKit'

async downloadFile() {
  const downloadConfig: request.DownloadConfig = {
    url: 'https://example.com/file.pdf',
    filePath: getContext().filesDir + '/downloads/report.pdf',
    header: {}
  }

  try {
    const downloadTask = await request.downloadFile(getContext(), downloadConfig)
    downloadTask.on('complete', () => {
      console.info('下载完成')
    })
    downloadTask.on('progress', (receivedSize, totalSize) => {
      const progress = (receivedSize / totalSize) * 100
      console.info(`下载进度:${progress.toFixed(2)}%`)
    })
  } catch (err) {
    console.error('下载异常:', JSON.stringify(err))
  }
}

九、综合案例实战

9.1 案例:简易电商首页

TypeScript
 
import { router } from '@kit.ArkUI'
import { httpRequest } from '../utils/HttpRequest'

interface Banner {
  id: number
  image: string
  link: string
}

interface Category {
  id: number
  name: string
  icon: string
}

interface Product {
  id: number
  name: string
  price: number
  originalPrice: number
  image: string
  tags: string[]
}

@Entry
@Component
struct HomePage {
  @State bannerList: Banner[] = []
  @State categoryList: Category[] = []
  @State productList: Product[] = []
  @State isLoading: boolean = true
  @State scrollY: number = 0

  aboutToAppear() {
    this.loadHomeData()
  }

  async loadHomeData() {
    // 模拟并发请求
    await Promise.all([
      this.loadBanners(),
      this.loadCategories(),
      this.loadProducts()
    ])
    this.isLoading = false
  }

  async loadBanners() {
    // 模拟数据
    this.bannerList = [
      { id: 1, image: 'https://example.com/b1.jpg', link: '' },
      { id: 2, image: 'https://example.com/b2.jpg', link: '' },
      { id: 3, image: 'https://example.com/b3.jpg', link: '' }
    ]
  }

  async loadCategories() {
    this.categoryList = [
      { id: 1, name: '手机', icon: 'icon_phone' },
      { id: 2, name: '电脑', icon: 'icon_pc' },
      { id: 3, name: '家电', icon: 'icon_home' },
      { id: 4, name: '服饰', icon: 'icon_clothes' },
      { id: 5, name: '食品', icon: 'icon_food' }
    ]
  }

  async loadProducts() {
    this.productList = [
      { id: 1, name: 'Mate 70 Pro', price: 6999, originalPrice: 7999, image: '', tags: ['新品', '热销'] },
      { id: 2, name: 'FreeBuds Pro 3', price: 1299, originalPrice: 1499, image: '', tags: ['降噪'] }
    ]
  }

  build() {
    Stack({ alignContent: Alignment.Top }) {
      Scroll() {
        Column({ space: 0 }) {
          // 轮播图
          this.BannerBuilder()

          // 分类入口
          this.CategoryBuilder()

          // 商品列表标题
          Row() {
            Text('爆款推荐')
              .fontSize(18)
              .fontWeight(FontWeight.Bold)
            Text('查看更多 >')
              .fontSize(14)
              .fontColor('#999999')
          }
          .width('100%')
          .justifyContent(FlexAlign.SpaceBetween)
          .padding({ left: 16, right: 16, top: 16, bottom: 12 })

          // 商品网格
          this.ProductGridBuilder()
        }
        .width('100%')
      }
      .width('100%')
      .height('100%')
      .scrollBar(BarState.Off)
      .onScroll((x, y) => {
        this.scrollY = y
      })

      // 顶部导航栏(滚动后显示背景)
      this.NavBarBuilder()
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }

  @Builder
  NavBarBuilder() {
    Row() {
      Text('鸿蒙商城')
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .fontColor(this.scrollY > 100 ? '#333333' : Color.White)

      Row() {
        Image($r('app.media.icon_search'))
          .width(20)
          .height(20)
          .fillColor(this.scrollY > 100 ? '#999999' : Color.White)
        Text('搜索商品')
          .fontSize(14)
          .fontColor(this.scrollY > 100 ? '#999999' : 'rgba(255,255,255,0.8)')
          .margin({ left: 8 })
      }
      .width(200)
      .height(36)
      .backgroundColor(this.scrollY > 100 ? '#F5F5F5' : 'rgba(255,255,255,0.2)')
      .borderRadius(18)
      .padding({ left: 12 })
      .layoutWeight(1)
      .margin({ left: 16, right: 16 })

      Badge({
        value: '3',
        position: BadgePosition.RightTop,
        style: { badgeSize: 14, badgeColor: '#FA2C2C' }
      }) {
        Image($r('app.media.icon_cart'))
          .width(24)
          .height(24)
          .fillColor(this.scrollY > 100 ? '#333333' : Color.White)
      }
    }
    .width('100%')
    .height(56)
    .padding({ left: 16, right: 16 })
    .backgroundColor(this.scrollY > 100 ? Color.White : Color.Transparent)
    .shadow({
      radius: this.scrollY > 100 ? 4 : 0,
      color: 'rgba(0,0,0,0.1)',
      offsetY: 2
    })
  }

  @Builder
  BannerBuilder() {
    Swiper() {
      ForEach(this.bannerList, (item: Banner) => {
        Image(item.image)
          .width('100%')
          .height(200)
          .objectFit(ImageFit.Cover)
      })
    }
    .width('100%')
    .height(200)
    .indicator(true)
    .autoPlay(true)
    .interval(3000)
    .loop(true)
  }

  @Builder
  CategoryBuilder() {
    Grid() {
      ForEach(this.categoryList, (item: Category) => {
        GridItem() {
          Column({ space: 8 }) {
            Image($r(`app.media.${item.icon}`))
              .width(48)
              .height(48)
              .borderRadius(24)
            Text(item.name).fontSize(13).fontColor('#666666')
          }
          .width('100%')
          .padding({ top: 16, bottom: 16 })
        }
      })
    }
    .width('100%')
    .height(100)
    .columnsTemplate('1fr 1fr 1fr 1fr 1fr')
    .backgroundColor(Color.White)
    .margin({ top: 12 })
  }

  @Builder
  ProductGridBuilder() {
    Grid() {
      ForEach(this.productList, (item: Product) => {
        GridItem() {
          Column() {
            Stack({ alignContent: Alignment.TopStart }) {
              Image(item.image)
                .width('100%')
                .aspectRatio(1)
                .objectFit(ImageFit.Cover)
                .borderRadius({ topLeft: 12, topRight: 12 })

              if (item.tags.length > 0) {
                Text(item.tags[0])
                  .fontSize(11)
                  .fontColor(Color.White)
                  .backgroundColor('#FF6600')
                  .padding({ top: 2, bottom: 2, left: 6, right: 6 })
                  .borderRadius({ topLeft: 12, bottomRight: 8 })
              }
            }
            .width('100%')

            Column({ space: 4 }) {
              Text(item.name)
                .fontSize(14)
                .maxLines(1)
                .textOverflow({ overflow: TextOverflow.Ellipsis })
                .width('100%')

              Row() {
                Text(`¥${item.price}`)
                  .fontSize(18)
                  .fontColor('#FF6600')
                  .fontWeight(FontWeight.Bold)
                Text(`¥${item.originalPrice}`)
                  .fontSize(12)
                  .fontColor('#999999')
                  .decoration({ type: TextDecorationType.LineThrough })
                  .margin({ left: 8 })
              }
              .width('100%')
            }
            .width('100%')
            .padding(12)
            .alignItems(HorizontalAlign.Start)
          }
          .width('100%')
          .backgroundColor(Color.White)
          .borderRadius(12)
        }
        .onClick(() => {
          router.pushUrl({ url: 'pages/ProductDetail', params: { id: item.id } })
        })
      })
    }
    .width('100%')
    .columnsTemplate('1fr 1fr')
    .rowsGap(12)
    .columnsGap(12)
    .padding(16)
  }
}

十、组件速查表

10.1 基础组件

表格
 
 
组件核心属性常用事件
Text fontSize, fontColor, fontWeight, maxLines, textOverflow
Image src, width, height, objectFit, borderRadius, alt onComplete, onError
Button type, fontSize, backgroundColor, stateEffect onClick
TextInput placeholder, type, maxLength, showPasswordIcon onChange, onSubmit
TextArea placeholder, maxLength onChange
LoadingProgress width, height, color
Progress value, total, type, color

10.2 选择组件

表格
 
 
组件说明关键属性
Toggle 开关 type: Switch/Checkbox, isOn, selectedColor
Checkbox 复选框 select, selectedColor
Radio 单选框 value, group, checked
Slider 滑块 value, min, max, step
DatePicker 日期选择 selected, lunar
TimePicker 时间选择 selected

10.3 容器组件

表格
 
 
组件排列方式核心属性
Column 垂直 space, alignItems, justifyContent
Row 水平 space, alignItems, justifyContent
Flex 弹性 direction, wrap, justifyContent, alignItems
Stack 层叠 alignContent
List 列表 space, divider, edgeEffect, scrollBar
Grid 网格 columnsTemplate, rowsTemplate, columnsGap, rowsGap
Scroll 滚动 scrollable, scrollBar, edgeEffect
Swiper 轮播 autoPlay, interval, loop, indicator
Tabs 标签页 barPosition, barWidth, barHeight
RelativeContainer 相对 alignRules

10.4 装饰器速查

表格
 
 
装饰器用途语法示例
@Entry 页面入口 @Entry struct Index {}
@Component 自定义组件 @Component struct MyComp {}
@State 内部状态 @State count: number = 0
@Prop 父传子(单向) @Prop title: string
@Link 父子双向 @Link value: number
@Provide 祖先提供 @Provide('theme') theme: string = 'light'
@Consume 后代消费 @Consume('theme') theme: string
@Observed 类深度监听 @Observed class User {}
@ObjectLink 对象链接 @ObjectLink user: User
@Watch 监听变化 @State @Watch('onChange') val: number = 0
@Builder 结构复用 @Builder Header() {}
@Styles 样式复用 @Styles function card() {}
@CustomDialog 自定义弹窗 @CustomDialog struct Alert {}

10.5 生命周期速查

表格
 
 
方法触发时机用途
aboutToAppear() 组件/页面创建 初始化数据、请求首屏
onPageShow() 页面显示 刷新数据、恢复状态
onPageHide() 页面隐藏 暂停计时器、保存草稿
aboutToDisappear() 组件/页面销毁 清理资源、取消请求
onBackPress() 返回键按下 拦截返回逻辑
onDidBuild() 布局完成 DOM 操作(API 12+)

附录:开发常见问题

Q1:图片不显示?

  • 网络图片:检查 module.json5 中是否声明 ohos.permission.INTERNET
  • 本地图片:确认图片放在 resources/base/media/ 目录,引用用 $r('app.media.xxx')
  • 图片格式:推荐 PNG、JPG、SVG

Q2:状态修改后 UI 不刷新?

  • 对象/数组属性变化:使用 @Observed + @ObjectLink
  • 数组整体赋值:this.list = [...this.list](创建新引用)
  • 对象整体赋值:this.obj = { ...this.obj, name: '新值' }

Q3:ForEach 报错或渲染异常?

  • 必须提供第三个参数(key 函数):ForEach(list, itemBuilder, (item) => item.id)
  • 确保 key 唯一且稳定

Q4:如何跳转页面并传参?

TypeScript
 
// 跳转
router.pushUrl({ url: 'pages/Detail', params: { id: 1 } })

// 接收
const params = router.getParams() as Record<string, object>
const id = params['id'] as number

Q5:如何获取屏幕宽高?

TypeScript
 
import { display } from '@kit.ArkUI'
const screenWidth = display.getDefaultDisplaySync().width
const screenHeight = display.getDefaultDisplaySync().height

文档版本:v1.0
最后更新:2026-07-25
适用 API:HarmonyOS API 12+
posted @ 2026-07-25 16:27  鬼门元歌  阅读(16)  评论(0)    收藏  举报