Appearance
自定义组件
@Component + build() 是 ArkTS 组件化的基础,理解其生命周期、状态、传参是掌握 ArkUI 的关键。
1. 自定义组件基本结构
typescript
@Component
struct MyComponent {
// 1. 状态变量
@State title: string = '标题'
@State count: number = 0
// 2. 生命周期
aboutToAppear() {
// 组件创建后、build 前执行(只执行一次)
console.log('组件创建')
}
aboutToDisappear() {
// 组件销毁前执行
console.log('组件销毁')
}
// 3. 构建函数
build() {
Column() {
Text(this.title)
.fontSize(24fp)
Button('点击')
.onClick(() => {
this.count++
})
Text(`计数: ${this.count}`)
}
.width('100%')
.height('100%')
}
}2. 组件的生命周期
2.1 组件级生命周期
| 阶段 | 函数 | 触发时机 | 执行次数 |
|---|---|---|---|
| 创建后 | aboutToAppear() | 组件创建完成,build 之前 | 1次 |
| 渲染中 | build() | 组件首次渲染或状态变化时 | N次 |
| 销毁前 | aboutToDisappear() | 组件被移除前 | 1次 |
typescript
@Component
struct LifecycleDemo {
aboutToAppear() {
// ✅ 可以做数据初始化
console.log('aboutToAppear: 组件即将渲染')
// ❌ 不能在 build 之前修改 @State 触发渲染
}
aboutToDisappear() {
// ✅ 清理资源
console.log('aboutToDisappear: 组件即将销毁')
}
build() {
Text('生命周期演示')
}
}2.2 页面级生命周期(@Entry 组件特有)
| 阶段 | 函数 | 触发时机 |
|---|---|---|
| 页面显示 | onPageShow() | 页面进入前台 |
| 页面隐藏 | onPageHide() | 页面离开前台 |
| 返回 | onBackPress() | 用户点击返回 |
typescript
@Entry
@Component
struct Index {
onPageShow() {
console.log('页面显示,刷新数据')
}
onPageHide() {
console.log('页面隐藏,暂停任务')
}
onBackPress() {
console.log('返回按钮被点击')
return false // false = 不拦截,true = 拦截返回
}
build() {
Column() {
Text('首页')
}
}
}3. 组件的嵌套
3.1 嵌套结构
typescript
// 父组件
@Component
struct Parent {
@State message: string = 'Hello from Parent'
build() {
Column() {
Text('父组件')
.fontSize(28fp)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 16 })
Child({ message: this.message })
}
.width('100%')
.height('100%')
.padding(16)
}
}
// 子组件
@Component
struct Child {
@Prop message: string = ''
build() {
Column() {
Text('子组件收到: ' + this.message)
.fontSize(18fp)
GrandChild()
}
.width('100%')
.padding(16)
.backgroundColor(Color.FromRGB(0xF5, 0xF5, 0xF5))
.borderRadius(8)
}
}
// 孙子组件
@Component
struct GrandChild {
build() {
Text('孙子组件')
.fontSize(14fp)
.fontColor(Color.Gray)
.margin({ top: 8 })
}
}3.2 组件嵌套的最佳实践
✅ 推荐:
├─ 嵌套不超过 3-4 层
├─ 每层职责单一
├─ 提取复杂逻辑为子组件
├─ 使用 @Builder 提取 UI 片段
❌ 避免:
├─ 一个组件超过 100 行
├─ 嵌套超过 4 层
├─ 组件职责混杂(既做 UI 又做逻辑)
└─ 在 build() 中写业务逻辑4. 组件的传参
4.1 @Prop — 父传子(单向)
typescript
// 子组件
@Component
struct UserInfo {
@Prop name: string = '未知'
@Prop age: number = 0
@Prop avatar: Resource = $r('app.media.default_avatar')
build() {
Row() {
Image(this.avatar).width(48).height(48).borderRadius(24)
Column() {
Text(this.name).fontSize(16fp)
Text(`${this.age}岁`).fontSize(14fp).fontColor(Color.Gray)
}.margin({ left: 12 })
}
}
}
// 父组件
@Entry
@Component
struct Index {
@State user: { name: string; age: number; avatar: Resource } = {
name: '小明',
age: 25,
avatar: $r('app.media.avatar')
}
build() {
Column() {
UserInfo({
name: this.user.name,
age: this.user.age,
avatar: this.user.avatar
})
}
}
}4.2 @Link — 父子双向绑定
typescript
// 子组件
@Component
struct NameInput {
@Link name: string // 双向绑定
build() {
TextInput({ placeholder: '请输入名字' })
.onChange((value: string) => {
this.name = value // 修改子组件 → 自动同步到父组件
})
}
}
// 父组件
@Entry
@Component
struct Index {
@Link pageName: string = '默认名称' // @Link 必须是 @State 或 @Link
build() {
Column() {
Text(`当前名字: ${this.pageName}`)
NameInput({ name: this.pageName }) // 双向绑定
}
}
}4.3 @State → @Prop → @Link 选择
| 场景 | 推荐 | 原因 |
|---|---|---|
| 组件内部管理数据 | @State | 私有状态 |
| 父→子单向传递 | @Prop | 单向拷贝 |
| 父↔子双向同步 | @Link | 双向引用 |
| 跨树共享 | @Provide/@Consume | 爷孙通信 |
5. @Builder 提取 UI 片段
5.1 基本用法
typescript
@Component
struct OrderCard {
@State orderNo: string = 'ORD001'
@State amount: number = 99.9
@State status: string = '已完成'
// 提取订单头部
@Builder
renderOrderHeader() {
Row() {
Text('订单号: ' + this.orderNo)
.fontSize(14fp)
.fontColor(Color.Gray)
Blank()
Text(this.status)
.fontSize(14fp)
.fontColor(Color.Green)
.backgroundColor(Color.FromRGBA(0, 0x80, 0, 0.1))
.padding({ left: 8, right: 8, top: 2, bottom: 2 })
.borderRadius(4)
}
.width('100%')
}
build() {
Column() {
this.renderOrderHeader()
Divider().margin({ top: 8, bottom: 8 })
Row() {
Text('金额: ')
Text(`¥${this.amount.toFixed(2)}`)
.fontSize(20fp)
.fontWeight(FontWeight.Bold)
.fontColor(Color.Red)
}
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(8)
}
}5.2 @Builder 带参
typescript
@Component
struct ListCard {
@State items: Array<{ name: string; price: number }> = []
// 带参数的 @Builder
@Builder
renderItem(item: { name: string; price: number }) {
Row() {
Text(item.name)
.fontSize(16fp)
Blank()
Text(`¥${item.price}`)
.fontSize(16fp)
.fontColor(Color.Red)
}
.width('100%')
.padding({ top: 12, bottom: 12 })
}
build() {
Column() {
ForEach(this.items, (item: any) => {
this.renderItem(item)
Divider()
})
}
}
}6. 组件化最佳实践
6.1 单一职责原则
✅ 好的组件:
├─ ComponentA.ts — 只做头部导航
├─ ComponentB.ts — 只做用户信息展示
├─ ComponentC.ts — 只做列表项
❌ 不好的组件:
└─ OneBigComponent.ts — 导航 + 用户 + 列表 + 弹窗(>500行)6.2 组件复用
typescript
// 复用组件:抽取为公共组件
// common/components/ActionButton.ets
@Component
export struct ActionButton {
@Prop text: string = ''
@Prop icon: Resource = $r('app.media.default')
@Prop onClick: () => void = () => {}
build() {
Row() {
Image(this.icon).width(20).height(20)
Text(this.text).fontSize(16fp).margin({ left: 8 })
}
.padding({ left: 16, right: 16, top: 10, bottom: 10 })
.backgroundColor(Color.White)
.borderRadius(8)
.onClick(this.onClick)
}
}typescript
// 使用公共组件
import { ActionButton } from '../../common/components/ActionButton'
@Entry
@Component
struct Index {
build() {
Column() {
ActionButton({
text: '拍照',
icon: $r('app.media.camera'),
onClick: () => this.takePhoto()
})
ActionButton({
text: '相册',
icon: $r('app.media.gallery'),
onClick: () => this.openGallery()
})
}
}
}7. 面试高频考点
Q1: 自定义组件的生命周期?
回答:组件级有 aboutToAppear(创建后)、build(渲染)、aboutToDisappear(销毁前)。页面级额外有 onPageShow、onPageHide、onBackPress。
Q2: build() 函数内有什么限制?
回答:只能写 UI 描述代码,不能写 console.log、不能定义局部变量、不能直接调用非 @Builder 装饰的函数。build 是纯 UI 描述。
Q3: 组件化有什么好处?
回答:代码复用、职责单一、易于维护、团队分工、可测试性、降低耦合。
🐱 小猫提示:自定义组件记住三件事:生命周期(aboutToAppear/build/aboutToDisappear)、传参(@State/@Prop/@Link)、@Builder 复用。面试必考。