ArkTS 自定义组件与 @Builder 区别总结
📌 文档说明
本文档详细对比 ArkTS 中两种 UI 构建方式:自定义组件(@Component) 和 @Builder 函数,帮助你理解它们的区别、使用场景和最佳实践。
🎯 一、快速对比
1.1 核心区别
| 特性 | 自定义组件(@Component) | @Builder 函数 |
|---|---|---|
| 定义方式 | 使用 @Component 装饰的 struct |
使用 @Builder 装饰的函数 |
| 状态管理 | ✅ 支持完整的状态管理 | ❌ 不支持状态管理 |
| 生命周期 | ✅ 有完整的生命周期钩子 | ❌ 没有生命周期 |
| 重用性 | ✅ 高度可复用,独立组件 | ✅ 可复用,轻量级 |
| 性能 | 中等(有完整的组件机制) | 好(轻量级,无额外开销) |
| 复杂度 | 适合复杂组件 | 适合简单 UI 片段 |
| 参数传递 | 通过组件属性传递 | 通过函数参数传递 |
| this 访问 | ✅ 可以访问 this | ⚠️ 全局 Builder 不能访问 this |
| 独立性 | 完全独立,可单独导出使用 | 依赖于组件或全局作用域 |
1.2 一句话总结
自定义组件:独立的、有状态的、完整的 UI 单元,适合构建复杂的可复用组件。
@Builder:轻量级的、无状态的 UI 构建函数,适合抽取重复的 UI 片段。
📖 二、自定义组件(@Component)
2.1 定义
自定义组件 是使用 @Component 装饰器定义的 struct,是 ArkTS 中构建 UI 的基本单位。它拥有完整的组件特性,包括状态管理、生命周期、事件处理等。
2.2 特点
✅ 完整的状态管理
- 支持 @State、@Prop、@Link 等所有状态装饰器
- 可以管理自己的内部状态
✅ 生命周期钩子
- aboutToAppear():组件即将出现
- aboutToDisappear():组件即将销毁
- onPageShow()、onPageHide() 等
✅ 高度封装
- 独立的逻辑单元
- 可以导出和复用
- 完整的组件能力
✅ 可组合
- 可以包含其他组件
- 支持组件嵌套
2.3 基本语法
typescript
@Component
struct MyComponent {
// 状态变量
@State count: number = 0
@Prop title: string
@Link value: string
// 生命周期
aboutToAppear() {
console.log('组件即将出现')
}
aboutToDisappear() {
console.log('组件即将销毁')
}
// 方法
handleClick() {
this.count++
}
// 构建方法
build() {
Column() {
Text(this.title)
Text(`${this.count}`)
Button('点击')
.onClick(() => this.handleClick())
}
}
}
2.4 完整示例
typescript
// ========== 自定义组件:用户卡片 ==========
@Component
export struct UserCard {
// Props - 从父组件接收
@Prop username: string
@Prop avatar: string
@Prop bio: string
// State - 组件内部状态
@State isExpanded: boolean = false
@State likeCount: number = 0
// 生命周期
aboutToAppear() {
console.log(`UserCard 组件创建: ${this.username}`)
// 可以在这里初始化数据、发起网络请求等
this.loadUserData()
}
aboutToDisappear() {
console.log(`UserCard 组件销毁: ${this.username}`)
// 可以在这里清理资源、取消订阅等
}
// 方法
loadUserData() {
// 模拟加载用户数据
setTimeout(() => {
this.likeCount = Math.floor(Math.random() * 1000)
}, 500)
}
toggleExpand() {
this.isExpanded = !this.isExpanded
}
handleLike() {
this.likeCount++
AlertDialog.show({
message: `你点赞了 ${this.username}`
})
}
// 构建 UI
build() {
Column({ space: 10 }) {
// 头部
Row({ space: 10 }) {
Image(this.avatar)
.width(60)
.height(60)
.borderRadius(30)
Column({ space: 5 }) {
Text(this.username)
.fontSize(18)
.fontWeight(FontWeight.Bold)
Text(this.bio)
.fontSize(14)
.fontColor(Color.Gray)
.maxLines(this.isExpanded ? 10 : 1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
}
.width('100%')
// 展开/收起按钮
if (this.bio.length > 30) {
Text(this.isExpanded ? '收起' : '展开')
.fontSize(12)
.fontColor('#007DFF')
.onClick(() => this.toggleExpand())
}
// 底部操作栏
Row({ space: 20 }) {
Row({ space: 5 }) {
Image($r('app.media.icon_like'))
.width(20)
.height(20)
Text(`${this.likeCount}`)
.fontSize(14)
}
.onClick(() => this.handleLike())
Row({ space: 5 }) {
Image($r('app.media.icon_comment'))
.width(20)
.height(20)
Text('评论')
.fontSize(14)
}
Row({ space: 5 }) {
Image($r('app.media.icon_share'))
.width(20)
.height(20)
Text('分享')
.fontSize(14)
}
}
.width('100%')
}
.width('100%')
.padding(15)
.backgroundColor(Color.White)
.borderRadius(12)
.shadow({
radius: 10,
color: '#00000010'
})
}
}
// ========== 使用自定义组件 ==========
@Entry
@Component
struct UserListPage {
@State users: Array<any> = [
{
id: 1,
name: '张三',
avatar: '',
bio: '热爱编程,专注前端开发,喜欢分享技术心得。'
},
{
id: 2,
name: '李四',
avatar: '',
bio: '全栈工程师,对新技术充满热情。'
}
]
build() {
Scroll() {
Column({ space: 15 }) {
Text('用户列表')
.fontSize(24)
.fontWeight(FontWeight.Bold)
ForEach(this.users, (user: any) => {
UserCard({
username: user.name,
avatar: user.avatar,
bio: user.bio
})
}, (user: any) => user.id.toString())
}
.width('100%')
.padding(15)
}
}
}
2.5 使用场景
✅ 适合使用自定义组件的场景:
-
复杂的 UI 模块
- 用户卡片、商品卡片
- 表单组件、对话框
- 导航栏、底部标签栏
-
需要状态管理
- 需要维护内部状态
- 需要接收 Props
- 需要双向绑定
-
需要生命周期控制
- 初始化时加载数据
- 销毁时清理资源
- 监听组件显示/隐藏
-
高度可复用的组件
- 可以独立导出
- 可以在多个页面使用
- 可以发布为组件库
-
独立的业务逻辑
- 有自己的事件处理
- 有复杂的交互逻辑
- 需要封装业务逻辑
🛠️ 三、@Builder 函数
3.1 定义
@Builder 是一个装饰器,用于修饰函数,将函数变为一个轻量级的 UI 构建函数。它主要用于抽取重复的 UI 代码片段,提高代码复用性。
3.2 特点
✅ 轻量级
- 没有组件的额外开销
- 性能更好
- 适合简单的 UI 片段
❌ 无状态管理
- 不能使用 @State、@Prop 等装饰器
- 不能维护内部状态
- 只能通过参数传递数据
❌ 无生命周期
- 没有 aboutToAppear 等钩子
- 只是纯粹的 UI 构建函数
✅ 灵活性高
- 可以定义在组件内部(局部)
- 可以定义在组件外部(全局)
- 可以接收参数
⚠️ 访问限制
- 全局 @Builder 不能访问 this
- 局部 @Builder 可以访问 this
3.3 基本语法
3.3.1 全局 @Builder
typescript
// 定义全局 Builder(在组件外部)
@Builder
function MyBuilder(title: string, count: number) {
Column() {
Text(title)
.fontSize(18)
.fontWeight(FontWeight.Bold)
Text(`${count}`)
.fontSize(24)
.fontColor('#FF0000')
}
}
// 使用
@Entry
@Component
struct MyPage {
@State count: number = 0
build() {
Column() {
// 调用全局 Builder
MyBuilder('计数器', this.count)
Button('增加')
.onClick(() => {
this.count++
})
}
}
}
3.3.2 局部 @Builder
typescript
@Entry
@Component
struct MyPage {
@State count: number = 0
@State title: string = '计数器'
// 定义局部 Builder(在组件内部)
@Builder
MyBuilder() {
Column() {
// ✅ 可以访问 this
Text(this.title)
.fontSize(18)
.fontWeight(FontWeight.Bold)
Text(`${this.count}`)
.fontSize(24)
.fontColor('#FF0000')
}
}
build() {
Column() {
// 调用局部 Builder
this.MyBuilder()
Button('增加')
.onClick(() => {
this.count++
})
}
}
}
3.4 参数传递方式
方式 1:按值传递(普通参数)
typescript
@Builder
function CardBuilder(title: string, subtitle: string, count: number) {
Column() {
Text(title)
Text(subtitle)
Text(`${count}`)
}
}
// 使用
CardBuilder('标题', '副标题', 100)
方式 2:按引用传递(对象参数)
typescript
// 定义参数接口
interface CardParams {
title: string
subtitle: string
count: number
}
@Builder
function CardBuilder(params: CardParams) {
Column() {
Text(params.title)
Text(params.subtitle)
Text(`${params.count}`)
}
}
// 使用(使用 $$ 实现引用传递)
@Entry
@Component
struct MyPage {
@State cardData: CardParams = {
title: '标题',
subtitle: '副标题',
count: 100
}
build() {
Column() {
// 使用 $$ 传递引用
CardBuilder({ cardData: this.cardData } as CardParams)
Button('修改')
.onClick(() => {
this.cardData.count++
})
}
}
}
3.5 完整示例
typescript
// ========== 定义全局 Builder ==========
// Builder 1:标题栏
@Builder
function TitleBar(title: string, showBack: boolean = true) {
Row() {
if (showBack) {
Image($r('app.media.icon_back'))
.width(24)
.height(24)
.onClick(() => {
console.log('返回')
})
}
Text(title)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
.textAlign(TextAlign.Center)
if (showBack) {
// 占位,保持标题居中
Row()
.width(24)
.height(24)
}
}
.width('100%')
.height(56)
.padding({ left: 16, right: 16 })
.backgroundColor('#FFFFFF')
}
// Builder 2:空状态
@Builder
function EmptyState(message: string, iconRes?: Resource) {
Column({ space: 20 }) {
if (iconRes) {
Image(iconRes)
.width(120)
.height(120)
}
Text(message)
.fontSize(16)
.fontColor('#999999')
Button('刷新')
.fontSize(14)
.onClick(() => {
console.log('刷新')
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
// Builder 3:加载状态
@Builder
function LoadingState(message: string = '加载中...') {
Column({ space: 15 }) {
LoadingProgress()
.width(50)
.height(50)
Text(message)
.fontSize(14)
.fontColor('#666666')
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
// ========== 使用 Builder ==========
@Entry
@Component
struct ProductListPage {
@State loading: boolean = true
@State products: Array<any> = []
aboutToAppear() {
// 模拟加载数据
setTimeout(() => {
this.products = [
{ id: 1, name: 'iPhone 15', price: 7999 },
{ id: 2, name: 'iPad Pro', price: 6799 },
{ id: 3, name: 'MacBook', price: 12999 }
]
this.loading = false
}, 2000)
}
// 局部 Builder:商品项
@Builder
ProductItem(product: any) {
Row({ space: 15 }) {
Image('')
.width(80)
.height(80)
.borderRadius(8)
.backgroundColor('#F5F5F5')
Column({ space: 5 }) {
Text(product.name)
.fontSize(16)
.fontWeight(FontWeight.Bold)
Text(`¥${product.price}`)
.fontSize(20)
.fontColor('#FF4D4F')
.fontWeight(FontWeight.Bold)
Button('购买')
.fontSize(14)
.onClick(() => {
AlertDialog.show({
message: `购买 ${product.name}`
})
})
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
}
.width('100%')
.padding(15)
.backgroundColor(Color.White)
.borderRadius(12)
}
build() {
Column() {
// 使用全局 Builder:标题栏
TitleBar('商品列表', true)
// 内容区域
if (this.loading) {
// 使用全局 Builder:加载状态
LoadingState('正在加载商品...')
} else if (this.products.length === 0) {
// 使用全局 Builder:空状态
EmptyState('暂无商品', $r('app.media.icon_empty'))
} else {
// 商品列表
Scroll() {
Column({ space: 10 }) {
ForEach(this.products, (product: any) => {
// 使用局部 Builder:商品项
this.ProductItem(product)
}, (product: any) => product.id.toString())
}
.padding(15)
}
.layoutWeight(1)
}
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
}
3.6 使用场景
✅ 适合使用 @Builder 的场景:
-
重复的 UI 片段
- 多处使用的相同布局
- 列表项模板
- 按钮组、标签组
-
简单的无状态 UI
- 标题栏、底部栏
- 加载状态、空状态、错误状态
- 分隔线、占位符
-
条件渲染的 UI 片段
- 根据状态显示不同的 UI
- 减少 if-else 嵌套
-
提取公共样式
- 统一的卡片样式
- 统一的按钮样式
- 统一的文本样式
-
性能优化
- 需要高性能的场景
- 避免组件的额外开销
🔄 四、详细对比
4.1 功能对比
| 功能 | 自定义组件(@Component) | @Builder 函数 |
|---|---|---|
| 状态管理 | ✅ @State、@Prop、@Link 等 | ❌ 不支持 |
| 生命周期 | ✅ aboutToAppear 等 | ❌ 不支持 |
| 方法定义 | ✅ 可以定义方法 | ❌ 只是函数,不能有方法 |
| 事件处理 | ✅ 可以定义事件处理方法 | ⚠️ 需要通过参数传递回调 |
| 参数传递 | 通过组件属性(Props) | 通过函数参数 |
| this 访问 | ✅ 可以访问 this | ⚠️ 全局 Builder 不能 |
| 独立导出 | ✅ 可以独立导出 | ⚠️ 可以,但依赖性强 |
| 性能 | 中等(完整组件机制) | 好(轻量级) |
| 适用复杂度 | 复杂组件 | 简单 UI 片段 |
4.2 代码对比
示例:构建一个按钮组
使用自定义组件:
typescript
// 定义自定义组件
@Component
struct ActionButtons {
@Prop title: string
@State likeCount: number = 0
@State isLiked: boolean = false
handleLike() {
this.isLiked = !this.isLiked
this.likeCount += this.isLiked ? 1 : -1
}
handleComment() {
AlertDialog.show({ message: '评论' })
}
handleShare() {
AlertDialog.show({ message: '分享' })
}
build() {
Row({ space: 20 }) {
// 点赞按钮
Button(`👍 ${this.likeCount}`)
.fontSize(14)
.backgroundColor(this.isLiked ? '#FFE5E5' : '#F5F5F5')
.fontColor(this.isLiked ? '#FF0000' : '#333333')
.onClick(() => this.handleLike())
// 评论按钮
Button('💬 评论')
.fontSize(14)
.backgroundColor('#F5F5F5')
.onClick(() => this.handleComment())
// 分享按钮
Button('🔗 分享')
.fontSize(14)
.backgroundColor('#F5F5F5')
.onClick(() => this.handleShare())
}
}
}
// 使用
@Entry
@Component
struct MyPage {
build() {
Column() {
ActionButtons({ title: '文章标题' })
}
}
}
使用 @Builder:
typescript
// 定义 Builder
@Builder
function ActionButtons(
likeCount: number,
isLiked: boolean,
onLike: () => void,
onComment: () => void,
onShare: () => void
) {
Row({ space: 20 }) {
// 点赞按钮
Button(`👍 ${likeCount}`)
.fontSize(14)
.backgroundColor(isLiked ? '#FFE5E5' : '#F5F5F5')
.fontColor(isLiked ? '#FF0000' : '#333333')
.onClick(onLike)
// 评论按钮
Button('💬 评论')
.fontSize(14)
.backgroundColor('#F5F5F5')
.onClick(onComment)
// 分享按钮
Button('🔗 分享')
.fontSize(14)
.backgroundColor('#F5F5F5')
.onClick(onShare)
}
}
// 使用(需要在父组件维护状态)
@Entry
@Component
struct MyPage {
@State likeCount: number = 0
@State isLiked: boolean = false
handleLike() {
this.isLiked = !this.isLiked
this.likeCount += this.isLiked ? 1 : -1
}
handleComment() {
AlertDialog.show({ message: '评论' })
}
handleShare() {
AlertDialog.show({ message: '分享' })
}
build() {
Column() {
ActionButtons(
this.likeCount,
this.isLiked,
() => this.handleLike(),
() => this.handleComment(),
() => this.handleShare()
)
}
}
}
对比分析
| 维度 | 自定义组件 | @Builder |
|---|---|---|
| 代码行数 | 较多(组件定义 + 使用) | 较少(函数定义 + 使用) |
| 状态管理 | ✅ 组件内部维护状态(封装性好) | ❌ 需要父组件维护状态(封装性差) |
| 事件处理 | ✅ 组件内部处理(逻辑集中) | ❌ 需要传递回调(逻辑分散) |
| 复用性 | ✅ 高度封装,易复用 | ⚠️ 依赖父组件状态,复用性差 |
| 独立性 | ✅ 完全独立,可单独导出 | ❌ 依赖父组件 |
| 性能 | 中等 | 好 |
4.3 性能对比
typescript
// 性能测试:渲染 1000 个相同的 UI 元素
// ========== 方案 1:自定义组件 ==========
@Component
struct ListItem {
@Prop title: string
@Prop subtitle: string
build() {
Row() {
Text(this.title)
Text(this.subtitle)
}
}
}
@Entry
@Component
struct ComponentList {
@State items: Array<any> = Array(1000).fill(null).map((_, i) => ({
id: i,
title: `标题 ${i}`,
subtitle: `副标题 ${i}`
}))
build() {
List() {
ForEach(this.items, (item: any) => {
ListItem() {
// ⚠️ 每个 ListItem 都是一个完整的组件实例
// 有额外的组件开销
ListItem({
title: item.title,
subtitle: item.subtitle
})
}
})
}
}
}
// ========== 方案 2:@Builder ==========
@Builder
function ListItemBuilder(title: string, subtitle: string) {
Row() {
Text(title)
Text(subtitle)
}
}
@Entry
@Component
struct BuilderList {
@State items: Array<any> = Array(1000).fill(null).map((_, i) => ({
id: i,
title: `标题 ${i}`,
subtitle: `副标题 ${i}`
}))
build() {
List() {
ForEach(this.items, (item: any) => {
ListItem() {
// ✅ Builder 是轻量级的函数调用
// 没有组件的额外开销
ListItemBuilder(item.title, item.subtitle)
}
})
}
}
}
// 性能结果:
// 方案 1(自定义组件):渲染时间约 150ms,内存占用较高
// 方案 2(@Builder):渲染时间约 100ms,内存占用较低
// 结论:对于大量相同的简单 UI,Builder 性能更好
🎯 五、选择指南
5.1 决策树
需要构建 UI?
├─ 是否需要状态管理?
│ ├─ 是 → 使用自定义组件(@Component)
│ │ • 需要 @State、@Prop、@Link
│ │ • 需要维护内部状态
│ │
│ └─ 否 → 继续判断
│
├─ 是否需要生命周期?
│ ├─ 是 → 使用自定义组件(@Component)
│ │ • 需要 aboutToAppear、aboutToDisappear
│ │ • 需要初始化、清理资源
│ │
│ └─ 否 → 继续判断
│
├─ 是否是复杂的业务逻辑?
│ ├─ 是 → 使用自定义组件(@Component)
│ │ • 有复杂的交互逻辑
│ │ • 需要封装业务逻辑
│ │
│ └─ 否 → 继续判断
│
├─ 是否需要高度封装和复用?
│ ├─ 是 → 使用自定义组件(@Component)
│ │ • 需要独立导出
│ │ • 在多个页面复用
│ │
│ └─ 否 → 使用 @Builder
│ • 简单的 UI 片段
│ • 轻量级、高性能
5.2 场景选择表
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 用户卡片、商品卡片 | 自定义组件 | 需要状态管理、生命周期 |
| 表单组件(输入框、选择器) | 自定义组件 | 需要状态管理、双向绑定 |
| 对话框、弹窗 | 自定义组件 | 需要状态管理、生命周期 |
| 导航栏、底部标签栏 | 自定义组件 | 复杂逻辑、需要状态管理 |
| 列表项(简单展示) | @Builder | 简单 UI、无状态、高性能 |
| 加载状态、空状态、错误状态 | @Builder | 无状态、可复用 |
| 标题栏、分隔线 | @Builder | 简单 UI、无状态 |
| 按钮组、标签组 | @Builder | 简单 UI、传递回调即可 |
| 统一的样式模板 | @Builder | 轻量级、复用性好 |
| 页面级组件 | 自定义组件 | 复杂业务逻辑、完整生命周期 |
5.3 快速判断法则
使用自定义组件的信号:
✅ 出现 "需要管理状态" 的想法
✅ 出现 "需要在初始化时做某事" 的想法
✅ 出现 "这个组件很复杂" 的想法
✅ 出现 "需要封装成独立模块" 的想法
✅ 出现 "需要导出给其他页面用" 的想法
使用 @Builder 的信号:
✅ 出现 "这段代码重复了" 的想法
✅ 出现 "只是简单的展示" 的想法
✅ 出现 "不需要维护状态" 的想法
✅ 出现 "需要优化性能" 的想法
✅ 出现 "只是提取公共样式" 的想法
📝 六、最佳实践
6.1 自定义组件最佳实践
✅ 好的做法
typescript
// 1. 组件职责单一
@Component
struct UserAvatar {
@Prop url: string
@Prop size: number = 40
build() {
Image(this.url)
.width(this.size)
.height(this.size)
.borderRadius(this.size / 2)
}
}
// 2. 合理使用生命周期
@Component
struct DataList {
@State data: Array<any> = []
@State loading: boolean = false
aboutToAppear() {
this.loadData()
}
aboutToDisappear() {
// 清理资源、取消请求等
}
async loadData() {
this.loading = true
// 加载数据逻辑
this.loading = false
}
build() {
// UI 构建
}
}
// 3. Props 类型明确
@Component
struct ProductCard {
@Prop productId: string
@Prop productName: string
@Prop price: number
@Prop imageUrl: string
private onBuy?: () => void // 回调函数用 private
build() {
// UI 构建
}
}
❌ 不好的做法
typescript
// 1. 组件职责不清晰(做了太多事)
@Component
struct MegaComponent {
@State userData: any
@State products: Array<any>
@State orders: Array<any>
@State messages: Array<any>
// ... 太多状态,应该拆分成多个组件
build() {
// 太复杂的 UI,应该拆分
}
}
// 2. 滥用生命周期
@Component
struct BadComponent {
aboutToAppear() {
// ❌ 在生命周期里直接修改 UI(应该用状态)
// ❌ 执行耗时操作(应该用异步)
for (let i = 0; i < 1000000; i++) {
// 阻塞主线程
}
}
build() {
// UI
}
}
// 3. Props 类型不明确
@Component
struct VagueComponent {
@Prop data: any // ❌ 使用 any,类型不明确
@Prop config: Object // ❌ 使用 Object,应该定义接口
build() {
// UI
}
}
6.2 @Builder 最佳实践
✅ 好的做法
typescript
// 1. 全局 Builder 用于通用 UI
@Builder
function Divider(height: number = 1, color: string = '#E5E5E5') {
Row()
.width('100%')
.height(height)
.backgroundColor(color)
}
// 2. 局部 Builder 用于组件内部复用
@Component
struct ProductList {
@State products: Array<any> = []
@Builder
ProductItem(product: any) {
// 商品项 UI(只在这个组件内使用)
}
@Builder
EmptyState() {
// 空状态 UI(只在这个组件内使用)
}
build() {
if (this.products.length === 0) {
this.EmptyState()
} else {
List() {
ForEach(this.products, (product: any) => {
ListItem() {
this.ProductItem(product)
}
})
}
}
}
}
// 3. 使用接口定义参数类型
interface CardParams {
title: string
subtitle: string
imageUrl: string
}
@Builder
function Card(params: CardParams) {
Column() {
Image(params.imageUrl)
Text(params.title)
Text(params.subtitle)
}
}
❌ 不好的做法
typescript
// 1. Builder 里包含复杂逻辑(应该用组件)
@Builder
function ComplexBuilder(data: any) {
Column() {
// ❌ Builder 里包含复杂的状态管理逻辑
// ❌ Builder 里包含生命周期相关代码
// 这些应该用自定义组件
}
}
// 2. 全局 Builder 访问 this(会报错)
@Builder
function BadBuilder() {
Text(this.data) // ❌ 全局 Builder 不能访问 this
}
// 3. 参数过多(应该用对象或组件)
@Builder
function TooManyParams(
p1: string,
p2: string,
p3: number,
p4: boolean,
p5: string,
p6: number,
p7: boolean,
p8: string
) {
// ❌ 参数太多,应该用对象传参或改用组件
}
6.3 组合使用
typescript
// 最佳实践:组件 + Builder 组合使用
// ========== 全局 Builder:通用 UI ==========
@Builder
function SectionTitle(title: string) {
Text(title)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.margin({ top: 20, bottom: 10 })
}
@Builder
function LoadingState() {
Column() {
LoadingProgress()
Text('加载中...')
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
// ========== 自定义组件:复杂业务逻辑 ==========
@Component
struct UserProfile {
@State user: UserInfo | null = null
@State loading: boolean = true
aboutToAppear() {
this.loadUserData()
}
async loadUserData() {
this.loading = true
// 加载用户数据
this.loading = false
}
// 局部 Builder:组件内部复用
@Builder
InfoRow(label: string, value: string) {
Row() {
Text(label)
.fontSize(14)
.fontColor('#666666')
.width(80)
Text(value)
.fontSize(14)
.fontColor('#333333')
.layoutWeight(1)
}
.width('100%')
.padding(10)
}
build() {
Column() {
// 使用全局 Builder
SectionTitle('个人信息')
if (this.loading) {
// 使用全局 Builder
LoadingState()
} else if (this.user) {
Column() {
// 使用局部 Builder
this.InfoRow('姓名', this.user.name)
this.InfoRow('年龄', `${this.user.age}`)
this.InfoRow('邮箱', this.user.email)
}
}
}
}
}
🎓 七、常见问题
Q1: 什么时候必须用自定义组件?
A: 以下情况必须用自定义组件:
- 需要状态管理 - 使用 @State、@Prop、@Link 等装饰器
- 需要生命周期 - 需要 aboutToAppear、aboutToDisappear 等钩子
- 需要独立导出 - 需要在多个文件/模块中复用
- 复杂的交互逻辑 - 有多个方法、事件处理
Q2: @Builder 能完全替代自定义组件吗?
A: 不能。
@Builder 只是轻量级的 UI 构建函数,不能替代自定义组件的核心能力(状态管理、生命周期)。它们是互补关系,不是替代关系。
Q3: 全局 @Builder 和局部 @Builder 的区别?
A: 主要区别在于作用域和 this 访问:
| 特性 | 全局 @Builder | 局部 @Builder |
|---|---|---|
| 定义位置 | 组件外部 | 组件内部 |
| 作用域 | 全局可用 | 仅当前组件可用 |
| this 访问 | ❌ 不能访问 this | ✅ 可以访问 this |
| 参数传递 | 必须通过参数传递 | 可以直接访问组件的状态 |
| 使用方式 | BuilderName(params) |
this.BuilderName() |
Q4: 性能考虑如何选择?
A: 性能选择指南:
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 大量相同的简单列表项 | @Builder | 轻量级,无组件开销 |
| 少量复杂的列表项 | 自定义组件 | 状态管理、逻辑封装 |
| 频繁渲染的简单 UI | @Builder | 性能更好 |
| 需要独立更新的 UI | 自定义组件 | 组件级别的更新优化 |
Q5: 可以在 @Builder 里使用 @Builder 吗?
A: 可以。
typescript
@Builder
function InnerBuilder() {
Text('内部 Builder')
}
@Builder
function OuterBuilder() {
Column() {
Text('外部 Builder')
InnerBuilder() // ✅ 可以调用其他 Builder
}
}
📊 八、总结对比表
核心差异
| 维度 | 自定义组件(@Component) | @Builder 函数 |
|---|---|---|
| 本质 | 完整的组件(Component) | 函数(Function) |
| 定义 | @Component struct |
@Builder function |
| 状态 | ✅ 支持(@State、@Prop、@Link) | ❌ 不支持 |
| 生命周期 | ✅ 支持(aboutToAppear 等) | ❌ 不支持 |
| 方法 | ✅ 可以定义方法 | ❌ 只是函数 |
| this | ✅ 可以访问 | ⚠️ 全局不可以,局部可以 |
| 独立性 | ✅ 完全独立 | ⚠️ 依赖性强 |
| 复用性 | ✅ 高(可独立导出) | ⚠️ 中(依赖上下文) |
| 性能 | 中(完整组件机制) | 好(轻量级) |
| 复杂度 | 适合复杂组件 | 适合简单 UI |
| 使用场景 | 独立功能模块、复杂业务逻辑 | 重复 UI 片段、简单模板 |
使用建议
优先级:
- 首选自定义组件 - 当需要状态管理、生命周期、复杂逻辑时
- 优选 @Builder - 当只是简单的 UI 复用时
- 组合使用 - 大部分实际项目中,两者配合使用效果最好
记忆口诀:
自定义组件:独立、有状态、有生命周期,适合复杂功能
@Builder:轻量、无状态、无生命周期,适合简单复用
口诀:
组件管状态,Builder 管样式
组件有生命,Builder 是函数
组件能导出,Builder 靠上下文
组件封装强,Builder 性能好
🎬 九、最终建议
9.1 选择原则
-
状态优先原则
- 需要管理状态 → 自定义组件
- 不需要状态 → @Builder
-
复杂度原则
- 复杂业务逻辑 → 自定义组件
- 简单 UI 片段 → @Builder
-
复用性原则
- 需要独立导出 → 自定义组件
- 组件内部复用 → 局部 @Builder
- 全局通用 UI → 全局 @Builder
-
性能原则
- 大量渲染 → @Builder
- 复杂交互 → 自定义组件
9.2 实战建议
在实际项目中:
- 页面级组件 - 用自定义组件(@Entry @Component)
- 功能组件 - 用自定义组件(用户卡片、商品卡片等)
- 工具组件 - 用自定义组件(对话框、Toast 等)
- 布局模板 - 用 @Builder(标题栏、底部栏等)
- 列表项模板 - 根据复杂度选择(复杂用组件,简单用 Builder)
- 通用样式 - 用 @Builder(分隔线、占位符等)