ArkTS 自定义组件与 @Builder 区别总结
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 基本语法
@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 完整示例
// ========== 自定义组件:用户卡片 ==========@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}`})}// 构建 UIbuild() {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
// 定义全局 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 = 0build() {Column() {// 调用全局 BuilderMyBuilder('计数器', this.count)Button('增加').onClick(() => {this.count++})}}
}
3.3.2 局部 @Builder
@Entry
@Component
struct MyPage {@State count: number = 0@State title: string = '计数器'// 定义局部 Builder(在组件内部)@BuilderMyBuilder() {Column() {// ✅ 可以访问 thisText(this.title).fontSize(18).fontWeight(FontWeight.Bold)Text(`${this.count}`).fontSize(24).fontColor('#FF0000')}}build() {Column() {// 调用局部 Builderthis.MyBuilder()Button('增加').onClick(() => {this.count++})}}
}
3.4 参数传递方式
方式 1:按值传递(普通参数)
@Builder
function CardBuilder(title: string, subtitle: string, count: number) {Column() {Text(title)Text(subtitle)Text(`${count}`)}
}// 使用
CardBuilder('标题', '副标题', 100)
方式 2:按引用传递(对象参数)
// 定义参数接口
interface CardParams {title: stringsubtitle: stringcount: 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 完整示例
// ========== 定义全局 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:商品项@BuilderProductItem(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 代码对比
示例:构建一个按钮组
使用自定义组件:
// 定义自定义组件
@Component
struct ActionButtons {@Prop title: string@State likeCount: number = 0@State isLiked: boolean = falsehandleLike() {this.isLiked = !this.isLikedthis.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:
// 定义 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 = falsehandleLike() {this.isLiked = !this.isLikedthis.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 性能对比
// 性能测试:渲染 1000 个相同的 UI 元素// ========== 方案 1:自定义组件 ==========
@Component
struct ListItem {@Prop title: string@Prop subtitle: stringbuild() {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 自定义组件最佳实践
✅ 好的做法
// 1. 组件职责单一
@Component
struct UserAvatar {@Prop url: string@Prop size: number = 40build() {Image(this.url).width(this.size).height(this.size).borderRadius(this.size / 2)}
}// 2. 合理使用生命周期
@Component
struct DataList {@State data: Array<any> = []@State loading: boolean = falseaboutToAppear() {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: stringprivate onBuy?: () => void // 回调函数用 privatebuild() {// UI 构建}
}
❌ 不好的做法
// 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 最佳实践
✅ 好的做法
// 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> = []@BuilderProductItem(product: any) {// 商品项 UI(只在这个组件内使用)}@BuilderEmptyState() {// 空状态 UI(只在这个组件内使用)}build() {if (this.products.length === 0) {this.EmptyState()} else {List() {ForEach(this.products, (product: any) => {ListItem() {this.ProductItem(product)}})}}}
}// 3. 使用接口定义参数类型
interface CardParams {title: stringsubtitle: stringimageUrl: string
}@Builder
function Card(params: CardParams) {Column() {Image(params.imageUrl)Text(params.title)Text(params.subtitle)}
}
❌ 不好的做法
// 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 组合使用
// 最佳实践:组件 + 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 = trueaboutToAppear() {this.loadUserData()}async loadUserData() {this.loading = true// 加载用户数据this.loading = false}// 局部 Builder:组件内部复用@BuilderInfoRow(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() {// 使用全局 BuilderSectionTitle('个人信息')if (this.loading) {// 使用全局 BuilderLoadingState()} else if (this.user) {Column() {// 使用局部 Builderthis.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: 可以。
@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(分隔线、占位符等)
