20 — 自定义组件与 @Builder/@BuilderParam

一、引言

breakpoint-system

当同一 UI 片段在多个页面反复出现(图标、头像、页签),将其封装为自定义组件是 ArkUI 声明式开发的基本功;而"占位 + 插槽"则让公共壳子具备无限扩展性。本项目通过自定义组件 MSVTextIcon、@Builder 构建函数与 @BuilderParam 插槽,实现了公共 Builder 复用与插槽化设计,支撑"同一页面多种外壳"的多设备策略。本文以 common/multishortvideobase/src/main/ets/components/MSVTextIcon.etsproducts/default/src/main/ets/components/Builders.etsfeatures/multishortvideoindividual/src/main/ets/components/Builders.ets 为主线。

二、自定义组件结构与生命周期

MSVTextIcon 将"图标 + 可选文字"组合为可配置组件,头像、点赞、搜索、分享等场景复用:

@ComponentV2
export struct MSVTextIcon {
  @Require @Param src: PixelMap | ResourceStr | DrawableDescriptor;
  @Param iconSize: Length = 32;
  @Param iconOnly: boolean = true;
  @Param content: string | Resource = '';
  @Param fontSize: ... = $r('sys.float.Body_M');
  @Param gap: Length = 2;

  build() {
    Column({ space: this.gap }) {
      Image(this.src).width(this.iconSize).aspectRatio(1).objectFit(ImageFit.Contain)
      if (!this.iconOnly) {
        Text(this.content).fontSize(this.fontSize).fontColor(this.fontColor)
      }
    }
  }
}

设计要点:@Require @Param 标记必传参数 src,编译期约束调用方;其余 @Param 提供默认值,调用方按需覆盖(如评论头像 iconSize: deviceInfo.deviceType === 'tv' ? 44 : 32);组件自带 aboutToAppear/aboutToDisappear 生命周期,可做数据加载与资源释放。

三、@Builder 局部构建函数

@Builder 将一段 UI 封装为可复用构建函数,支持参数传递。MSVTabs 用它定义页签样式:

@Builder
MSVTabBar(params: MSVDataModel, currentIndex: number) {
  if (params.iconOnly) {
    Image(this.isDark ? params.iconDark : params.icon)
      .width(params.iconSize).aspectRatio(1)
  } else {
    Column() {
      Text(params.text)
        .fontColor(this.activeIndex === currentIndex ? this.selectedLightColor : this.lightColor)
    }
    .border({ width: { bottom: this.showBarUnderline && (this.activeIndex === currentIndex) ? 1 : 0 } })
  }
}

SplitComment 用 @Builder 定义自定义标题栏(渐变遮罩 + 标题 + 关闭按钮):

@Builder
CustomTitleBuilder() {
  Column() {
    Column().width(CommonConstants.FULL_PERCENT).height(36)
      .linearGradient({ direction: GradientDirection.Bottom, colors: [['#4D000000', 0.0], ['#00000000', 1.0]] })
    Row() {
      Text($r('app.string.comment_title', this.commentCount))
      Row() { SymbolGlyph($r('sys.symbol.xmark')).fontSize(18) }
        .onClick(() => { this.pathStack.pop(); this.showSideComment = false; })
    }
  }
}

四、@BuilderParam 内容插槽

@BuilderParam 接收外部传入的 Builder,形成"占位-填充"插槽。MSVTabs 内部封装 MSVTabContent,让每个 TabContent 的内容由调用方决定:

@ComponentV2
struct MSVTabContent {
  @Local stackId: string = 'MSVTabContent';
  @BuilderParam content: BuilderCallback;   // 外部注入的页签内容

  build() {
    Stack() {
      this.content()
    }
    .id(this.stackId)
  }
}

使用侧把页签内容 Builder 传入:

@Builder
MSVTabContent(data: MSVDataModel[]) {
  ForEach(data, (tabItem: MSVDataModel, index: number) => {
    TabContent() {
      if (tabItem.content) {
        MSVTabContent({ content: tabItem.content })
      }
    }
    .tabBar(this.MSVTabBar(tabItem, index))
  }, (tabItem: MSVDataModel) => JSON.stringify(tabItem.content))
}

BuilderCallback 是 ArkUI 通用构建函数类型,@BuilderParam 与导出 @Builder 天然兼容。

五、Builders.ets 公共 Builder 复用

产品层将页面级 Builder 统一收敛在 Builders.ets,作为 MSVDataModel.content 注入页签,实现"数据驱动 + 内容可换":

// products/default/.../components/Builders.ets
@Builder export function home() {
  SubTabs().width(CommonConstants.FULL_PERCENT).height(CommonConstants.FULL_PERCENT)
    .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
}

@Builder export function recommend() {
  Column() { AdaptiveVideoForDefault() }
    .width(CommonConstants.FULL_PERCENT).height(CommonConstants.FULL_PERCENT)
    .backgroundColor(Color.Black)
}

individual 模块的 Builders.ets 同样导出作品页等:@Builder export function works() { Column() { Works() } ... }。数据层直接引用 Builder 完成页签组装(MainTabsViewModel):

this.mainTabsData.push(new MSVDataModel(home, $r('app.string.home_title')));
this.mainTabsData.push(new MSVDataModel(add, '', true, $r('app.media.ic_plus'), $r('app.media.ic_plus_dark')));

六、插槽化设计在个人作品页与评论区头部的应用

个人作品页 Individual.ets 的头部信息区与 MSVTabs 内容区解耦;评论区头部(CustomTitleBuilder)与评论列表(Comment)解耦。二者共享同一模式:外壳组件只负责布局与关闭逻辑,业务内容通过 @Builder/插槽注入,实现"同一评论页面,手机半模态、大屏分栏、TV 全屏三种外壳"下内容零重复。

应用场景壳组件插槽/组合内容多形态

评论区SplitComment / bindSheetComment半模态、分栏、全屏
个人主页IndividualByRouterIndividual全屏、侧面板
底部页签MSVTabs + MSVTabContentBuilders.ets 各页面手机/TV/PC

七、总结与最佳实践

  • 高频 UI 片段封装为自定义组件(MSVTextIcon),必传参数用 @Require @Param,可选参数给默认值。
  • 组件内复用用 @Builder,跨模块复用用导出 @Builder(Builders.ets)。
  • @BuilderParam 提供插槽能力,壳组件与业务内容彻底解耦,支撑"一内容多外壳"。
  • 页面内容以 Builder 注入 MSVDataModel,实现页签数据驱动、内容可热替换。
  • 生命周期方法保持轻量,aboutToAppear 只做数据加载,避免卡顿。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐