自定义组件
00:00
自定义组件与生命周期
概述
自定义组件是 HarmonyOS 应用构建的基础单元。通过 @Component 装饰器定义组件,使用 @Prop/@Link 实现组件间通信,配合生命周期回调管理资源。良好的组件设计是构建可维护应用的关键。
基础概念
组件生命周期
| 回调 | 说明 | 触发时机 |
|---|---|---|
| aboutToAppear | 组件即将出现 | 构建自定义组件时 |
| aboutToDisappear | 组件即将销毁 | 组件被移除时 |
| onPageShow | 页面显示 | 页面切入前台 |
| onPageHide | 页面隐藏 | 页面切到后台 |
| onBackPress | 返回键按下 | 用户按返回键 |
组件通信方式
| 方式 | 装饰器 | 数据流向 | 适用场景 |
|---|---|---|---|
| 属性传递 | @Prop | 父 -> 子 | 只读数据展示 |
| 双向绑定 | @Link | 父 <-> 子 | 子组件需修改父数据 |
| 事件回调 | 函数属性 | 子 -> 父 | 通知父组件 |
| 跨层级 | @Provide | 祖先 -> 后代 | 深层嵌套通信 |
快速上手
基本自定义组件
// 定义可复用的按钮组件
@Component
export struct MyButton {
@Prop text: string = '按钮'
@Prop bgColor: string = '#007DFF'
onButtonClick?: () => void // 事件回调
build() {
Button(this.text)
.width(200)
.height(50)
.backgroundColor(this.bgColor)
.fontColor(Color.White)
.borderRadius(25)
.onClick(() => this.onButtonClick?.())
}
}
// 使用自定义组件
@Entry
@Component
struct ButtonPage {
@State count: number = 0
build() {
Column() {
Text(`计数: ${this.count}`).fontSize(30)
MyButton({
text: '增加',
onButtonClick: () => this.count++
})
MyButton({
text: '减少',
bgColor: '#FF4444',
onButtonClick: () => this.count--
})
}
}
}
带生命周期的组件
@Component
struct Timer {
@Prop duration: number = 60
@State remaining: number = 60
private intervalId: number = -1
// 组件即将出现:启动定时器
aboutToAppear() {
this.remaining = this.duration
this.intervalId = setInterval(() => {
if (this.remaining > 0) {
this.remaining--
} else {
clearInterval(this.intervalId)
}
}, 1000)
}
// 组件即将销毁:清理定时器
aboutToDisappear() {
if (this.intervalId !== -1) {
clearInterval(this.intervalId)
}
}
build() {
Text(`${this.remaining}s`)
.fontSize(40)
.fontColor(this.remaining <= 10 ? Color.Red : Color.Black)
}
}
详细用法
卡片组件
// 通用的卡片组件
@Component
struct Card {
@Prop title: string = ''
@Prop subtitle: string = ''
@Prop showAction: boolean = false
onAction?: () => void
@BuilderParam content: () => void // 插槽:自定义内容
build() {
Column() {
// 卡片头部
Row() {
Column() {
Text(this.title).fontSize(18).fontWeight(FontWeight.Bold)
if (this.subtitle) {
Text(this.subtitle).fontSize(14).fontColor('#999').margin({ top: 4 })
}
}.layoutWeight(1)
if (this.showAction) {
Text('更多').fontColor('#007DFF').onClick(() => this.onAction?.())
}
}.width('100%').padding({ left: 16, right: 16, top: 16 })
// 卡片内容(插槽)
Column() {
this.content()
}.padding(16)
}
.width('100%')
.borderRadius(12)
.backgroundColor(Color.White)
.shadow({ radius: 5, color: '#1F000000', offsetY: 2 })
}
}
// 使用
@Entry
@Component
struct CardPage {
build() {
Column() {
Card({ title: '用户信息', subtitle: '基本信息' }) {
Text('这是卡片内容区域')
}
Card({ title: '订单列表', showAction: true, onAction: () => {
// 查看更多
}}) {
Text('订单1')
Text('订单2')
}
}.padding(16)
}
}
列表项组件
interface ContactItem {
id: number
name: string
phone: string
avatar: string
}
@Component
struct ContactRow {
@Prop item: ContactItem = { id: 0, name: '', phone: '', avatar: '' }
onItemClick?: (id: number) => void
onItemLongPress?: (id: number) => void
build() {
Row() {
Image(this.item.avatar)
.width(50).height(50).borderRadius(25)
Column() {
Text(this.item.name).fontSize(16).fontWeight(FontWeight.Medium)
Text(this.item.phone).fontSize(14).fontColor('#999').margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
.margin({ left: 12 })
}
.width('100%')
.height(70)
.padding({ left: 16, right: 16 })
.onClick(() => this.onItemClick?.(this.item.id))
.gesture(LongPressGesture().onAction(() => this.onItemLongPress?.(this.item.id)))
}
}
常见场景
加载状态组件
@Component
struct LoadingView {
@Prop isLoading: boolean = false
@Prop isEmpty: boolean = false
@Prop errorMsg: string = ''
@BuilderParam content: () => void
build() {
if (this.isLoading) {
Column() {
LoadingProgress().width(50).height(50).color('#007DFF')
Text('加载中...').fontSize(14).fontColor('#999').margin({ top: 10 })
}.width('100%').height('100%').justifyContent(FlexAlign.Center)
} else if (this.errorMsg) {
Column() {
Text(this.errorMsg).fontSize(16).fontColor('#999')
Button('重试').margin({ top: 20 })
}.width('100%').height('100%').justifyContent(FlexAlign.Center)
} else if (this.isEmpty) {
Column() {
Text('暂无数据').fontSize(16).fontColor('#999')
}.width('100%').height('100%').justifyContent(FlexAlign.Center)
} else {
this.content()
}
}
}
注意事项
- @Component 修饰的 struct 不支持继承,组件复用应使用组合和 @BuilderParam
- aboutToAppear 中可以访问 @State 变量,适合做初始化
- aboutToDisappear 中必须清理定时器、监听器等资源,避免内存泄漏
- @BuilderParam 只能有一个,用于定义组件的内容插槽
- 组件的属性传递是只读的,子组件不能直接修改父组件传入的非 @Link 属性
- 事件回调使用可选链调用(?.()),避免回调未定义时报错
进阶用法
泛型组件模拟
// ArkTS 不支持泛型组件,但可以通过接口约束实现类似效果
interface ListDataSource {
id: string
title: string
subtitle?: string
}
@Component
struct GenericList {
@Prop items: ListDataSource[] = []
@BuilderParam itemBuilder: (item: ListDataSource) => void
build() {
List() {
ForEach(this.items, (item: ListDataSource) => {
ListItem() {
this.itemBuilder(item)
}
}, (item: ListDataSource) => item.id)
}
}
}