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 基本语法

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 使用场景

✅ 适合使用自定义组件的场景:

  1. 复杂的 UI 模块

    • 用户卡片、商品卡片
    • 表单组件、对话框
    • 导航栏、底部标签栏
  2. 需要状态管理

    • 需要维护内部状态
    • 需要接收 Props
    • 需要双向绑定
  3. 需要生命周期控制

    • 初始化时加载数据
    • 销毁时清理资源
    • 监听组件显示/隐藏
  4. 高度可复用的组件

    • 可以独立导出
    • 可以在多个页面使用
    • 可以发布为组件库
  5. 独立的业务逻辑

    • 有自己的事件处理
    • 有复杂的交互逻辑
    • 需要封装业务逻辑

🛠️ 三、@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 的场景:

  1. 重复的 UI 片段

    • 多处使用的相同布局
    • 列表项模板
    • 按钮组、标签组
  2. 简单的无状态 UI

    • 标题栏、底部栏
    • 加载状态、空状态、错误状态
    • 分隔线、占位符
  3. 条件渲染的 UI 片段

    • 根据状态显示不同的 UI
    • 减少 if-else 嵌套
  4. 提取公共样式

    • 统一的卡片样式
    • 统一的按钮样式
    • 统一的文本样式
  5. 性能优化

    • 需要高性能的场景
    • 避免组件的额外开销

🔄 四、详细对比

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: 以下情况必须用自定义组件:

  1. 需要状态管理 - 使用 @State、@Prop、@Link 等装饰器
  2. 需要生命周期 - 需要 aboutToAppear、aboutToDisappear 等钩子
  3. 需要独立导出 - 需要在多个文件/模块中复用
  4. 复杂的交互逻辑 - 有多个方法、事件处理

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 片段、简单模板

使用建议

优先级:

  1. 首选自定义组件 - 当需要状态管理、生命周期、复杂逻辑时
  2. 优选 @Builder - 当只是简单的 UI 复用时
  3. 组合使用 - 大部分实际项目中,两者配合使用效果最好

记忆口诀:

复制代码
自定义组件:独立、有状态、有生命周期,适合复杂功能
@Builder:轻量、无状态、无生命周期,适合简单复用

口诀:
组件管状态,Builder 管样式
组件有生命,Builder 是函数
组件能导出,Builder 靠上下文
组件封装强,Builder 性能好

🎬 九、最终建议

9.1 选择原则

  1. 状态优先原则

    • 需要管理状态 → 自定义组件
    • 不需要状态 → @Builder
  2. 复杂度原则

    • 复杂业务逻辑 → 自定义组件
    • 简单 UI 片段 → @Builder
  3. 复用性原则

    • 需要独立导出 → 自定义组件
    • 组件内部复用 → 局部 @Builder
    • 全局通用 UI → 全局 @Builder
  4. 性能原则

    • 大量渲染 → @Builder
    • 复杂交互 → 自定义组件

9.2 实战建议

在实际项目中:

  1. 页面级组件 - 用自定义组件(@Entry @Component)
  2. 功能组件 - 用自定义组件(用户卡片、商品卡片等)
  3. 工具组件 - 用自定义组件(对话框、Toast 等)
  4. 布局模板 - 用 @Builder(标题栏、底部栏等)
  5. 列表项模板 - 根据复杂度选择(复杂用组件,简单用 Builder)
  6. 通用样式 - 用 @Builder(分隔线、占位符等)
相关推荐
SWUT胖虎4 小时前
ArkTS 中 @State 底层原理详解
java·list·harmonyos·鸿蒙
北风江畔(LuckyClover)6 小时前
鸿蒙应用开发(第一章:快速体验)
华为·harmonyos
大雷神6 小时前
HarmonyOS Canvas开发指南
harmonyos
北风江畔(LuckyClover)6 小时前
手戳一个HarmonyOS (鸿蒙)移动应用
华为·harmonyos
SWUT胖虎17 小时前
AlphabetIndexer组件 与 List 联动总结
list·harmonyos·arkts·鸿蒙
鸿蒙小白龙19 小时前
OpenHarmony轻量级内核LiteOS-M技术详解与应用实践
harmonyos·鸿蒙·鸿蒙系统·open harmony
Damon小智21 小时前
HarmonyOS应用开发-低代码开发登录页面(超详细)
低代码·harmonyos·鸿蒙·登录·arcts·arcui·griditem
爱笑的眼睛111 天前
深入探讨HarmonyOS中ListItem的滑动操作:从基础实现到高级分布式交互
华为·harmonyos
摘星编程1 天前
【参赛心得】HarmonyOS创新赛获奖秘籍:如何用Stage模型和声明式UI打造高分作品
ui·华为·harmonyos·鸿蒙开发·stage模型