HarmonyOS 应用开发《掌上英语》第17篇:声明式 UI 构建范式——ArkTS 的 @Builder 装饰器深入
声明式 UI 构建范式——ArkTS 的 @Builder 装饰器深入

引言
在 HarmonyOS 的 ArkTS 声明式 UI 框架中,@Builder 装饰器是一个极其重要的构建工具。它允许开发者将 UI 片段封装为可复用的构建函数,从而避免在 build() 方法中编写臃肿的嵌套代码。本文将通过我们英语学习 App 的实际源码,深入剖析 @Builder 的四种使用场景:组件内 Builder、全局 Builder、参数传递、以及导出复用。
一、@Builder 的基本概念
@Builder 是 ArkTS 提供的 UI 构建函数装饰器,它的核心特性有:
- 封装 UI 片段:将一个独立的 UI 区块封装成一个函数,在
build()中直接调用 - 支持两种定义位置:组件内部定义为成员方法,或组件外部定义为全局函数
- 支持参数传递:可以通过参数向 Builder 传入数据
- 具备响应式能力:Builder 内部引用组件的
@Local/@Param变量时,变化会自动触发重绘
在我们的 MainPage.ets 中,总共使用了 6 个 @Builder 来组织首页的各个 UI 区块,形成了一套清晰的 UI 分层结构:
HomePage (主组件)
├── topBarBuilder() — 顶部导航栏
├── todayStudyBuilder() — 今日学习卡片
├── swiperBuilder() — 轮播 Banner
├── functionBarBuilder() — 5 宫格功能栏
├── practiceModeBuilder() — 2×2 练习模式
└── subjectPracticeBuilder() — 学习进度区
二、组件内 @Builder
2.1 基础用法
组件内的 @Builder 方法可以直接访问当前组件的所有属性和状态变量。以下是我们 HomePage 中的 topBarBuilder():
@ComponentV2
export struct HomePage {
// ...
build() {
Column() {
// 直接调用 Builder
this.topBarBuilder();
Scroll() {
Column({ space: 10 }) {
this.todayStudyBuilder();
this.swiperBuilder();
this.functionBarBuilder();
this.practiceModeBuilder();
this.subjectPracticeBuilder();
}
}
// ...
}
}
@Builder
topBarBuilder() {
Row() {
Text('英语学习')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.layoutWeight(1);
Image($r('app.media.ic_practice_camera'))
.width(28)
.height(28)
.onClick(() => {
RouterModule.push({ url: RouterMap.SEARCH_INPUT_PAGE });
});
}
.width('100%')
.height(56)
.padding({ left: 4, right: 4 });
}
// ...
}
关键点解析:
- Builder 内部直接使用
this.functions、this.dataSource等组件状态变量 - 调用时只需写
this.topBarBuilder(),无需传递任何参数 - Builder 内引用的状态变量变化时,Builder 会自动触发重新渲染
2.2 访问组件状态的典型示例
todayStudyBuilder() 展示了如何通过 Builder 读取组件数据并渲染 UI:
@Builder
todayStudyBuilder() {
Column() {
Row() {
Text('今日学习')
.fontSize(16)
.fontWeight(FontWeight.Medium);
Text('坚持 7 天')
.fontSize(12)
.fontColor($r('sys.color.font_secondary'))
.layoutWeight(1)
.textAlign(TextAlign.End);
}
.width('100%')
.margin({ bottom: 8 });
Row() {
Text('已学 15 词 / 目标 30 词')
.fontSize(13)
.fontColor($r('sys.color.font_secondary'))
.layoutWeight(1);
Text('50%')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor($r('sys.color.comp_background_emphasize'));
}
.width('100%')
.margin({ bottom: 8 });
Progress({ value: 15, total: 30, type: ProgressType.Linear })
.color($r('sys.color.comp_background_emphasize'))
.height(8)
.width('100%');
}
.padding(16)
.borderRadius(16)
.backgroundColor($r('sys.color.background_primary'));
}
这个 Builder 封装了一个完整的学习进度卡片,包含标题行、数据行和进度条,形成了一个独立的视觉模块。
三、全局 @Builder
当一个 Builder 需要被多个组件复用时,可以将其定义在组件外部,成为全局 Builder。
3.1 全局 Builder 的定义
在 MainPage.ets 的末尾,我们定义了多个全局 Builder:
// 全局 Builder:直接返回一个组件实例
@Builder
export function HomePageBuilder() {
HomePage();
}
// 全局 Builder:封装核心功能图标
@Builder
export function CourseCoreBuilder(mainFunctionItem: CourseCoreBar) {
Column() {
Image(mainFunctionItem.showIcon)
.height($r('app.float.vp_24'))
.width($r('app.float.vp_24'))
.margin({ bottom: $r('app.float.vp_2') });
Text(mainFunctionItem.description)
.fontSize($r('sys.float.Caption_M'));
}
.width('100%')
.height(72)
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.stateStyles({ pressed: { .scale({ x: 0.97, y: 0.97 }) } })
.onClick(() => {
// 路由跳转逻辑
// ...
});
}
// 全局 Builder:封装课程练习卡片
@Builder
export function CoursePracticeBuilder(questions: CourseQuestions) {
Column() {
Row() {
Text(questions.subjectName)
.fontSize($r('sys.float.Subtitle_S'))
.fontWeight(FontWeight.Medium);
// ...
}
// ...
}
}
3.2 全局 Builder 的导出与导入
使用 export 关键字可以将 Builder 导出,供其他模块使用:
// 在 RouterTable 注册页面
@Builder
export function HomePageBuilder() {
HomePage();
}
在路由表中,可以这样注册:
RouterTable.register('HomePage', wrapBuilder(HomePageBuilder));
同样在 CourseHomePage.ets 中也有类似用法:
@Builder
export function CourseHomePageBuilder() {
CourseHomePage();
}
四、@Builder 的参数传递
4.1 传递数据对象
全局 Builder CourseCoreBuilder 展示如何传递一个自定义对象:
@Builder
export function CourseCoreBuilder(mainFunctionItem: CourseCoreBar) {
Column() {
Image(mainFunctionItem.showIcon)
.height($r('app.float.vp_24'))
.width($r('app.float.vp_24'));
Text(mainFunctionItem.description)
.fontSize($r('sys.float.Caption_M'));
}
// ...
}
在 functionBarBuilder 中调用:
@Builder
functionBarBuilder() {
Grid() {
ForEach(this.functions, (item: CourseCoreBar) => {
GridItem() {
// 传递 item 参数给全局 Builder
CourseCoreBuilder(item);
};
}, (item: CourseCoreBar, index: number) => item.description + index);
}
.columnsTemplate('1fr 1fr 1fr 1fr 1fr')
// ...
}
4.2 传递基础类型
全局 Builder CoursePracticeBuilder 接收 CourseQuestions 对象,展示了更复杂的数据传递:
@Builder
export function CoursePracticeBuilder(questions: CourseQuestions) {
Column() {
Row() {
Text(questions.subjectName)
.fontSize($r('sys.float.Subtitle_S'))
.fontWeight(FontWeight.Medium);
};
Row() {
Text('开始学习')
.fontSize($r('sys.float.Caption_L'))
.fontWeight(FontWeight.Regular)
.fontColor($r('sys.color.font_secondary'));
Image($r('app.media.icon_right'))
.width($r('app.float.vp_16'))
.height($r('app.float.vp_16'));
}.layoutWeight(1)
.justifyContent(FlexAlign.End);
Row() {
Text('已掌握')
.margin({ right: 8 })
.fontSize($r('sys.float.Body_S'));
Text() {
Span(questions.masteredWords + '')
.fontColor($r('sys.color.comp_background_emphasize'));
Span(' / ' + questions.totalWords + '');
}
.fontSize($r('sys.float.Body_S'));
}
// ...
}
}
4.3 参数传递规范
| 参数类型 | 示例 | 说明 |
|---|---|---|
| 自定义对象 | CourseCoreBuilder(item) |
推荐,结构清晰 |
| 基础类型 | Text('hello') |
适用于简单场景 |
| Resource 类型 | $r('app.media.ic_home') |
用于资源引用 |
五、复用粒度控制
5.1 何时使用 Builder vs 子组件
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| UI 片段 < 30 行 | @Builder |
轻量、无额外上下文开销 |
| UI 片段 30-50 行 | @Builder 或子组件 |
视复用范围而定 |
| UI 片段 > 50 行 | 独立 @Component |
便于维护和测试 |
| 需要在多页面复用 | 全局 @Builder + export |
模块化共享 |
| 需要独立生命周期 | 独立 @Component |
Builder 不拥有生命周期 |
5.2 实际项目中的粒度选择
在我们的项目中,Banner 组件因为需要独立的响应式逻辑和生命周期,所以抽成了独立组件:
@Builder
swiperBuilder() {
Banner(); // 直接复用 Banner 子组件
}
而 topBarBuilder、todayStudyBuilder 等 UI 片段因为相对简单(20-40 行),且仅在 HomePage 中使用,所以保留为组件内 Builder,保持了良好的封装性。
六、最佳实践
6.1 Builder 命名规范
- 组件内 Builder 使用
camelCase命名,以Builder结尾:topBarBuilder、todayStudyBuilder - 全局 Builder 使用
PascalCase命名,以Builder结尾:HomePageBuilder、CourseCoreBuilder
6.2 避免 Builder 嵌套过深
如果 Builder 内部逻辑变得复杂,应将其拆解为多个小型 Builder:
// ❌ 不推荐:一个 Builder 包含太多逻辑
@Builder
complexBuilder() { /* 100 行代码 */ }
// ✅ 推荐:拆分为多个小型 Builder
@Builder
partA() { /* 30 行 */ }
@Builder
partB() { /* 30 行 */ }
6.3 导出 Builder 的最佳实践
在路由注册场景中,习惯使用全局 Builder 包装页面组件:
// HomePage.ets 末尾
@Builder
export function HomePageBuilder() {
HomePage();
}
// CourseHomePage.ets 末尾
@Builder
export function CourseHomePageBuilder() {
CourseHomePage();
}
这种方式比直接导出组件更灵活,允许在实际调用时注入参数或包装逻辑。
总结
@Builder 装饰器是 ArkTS 声明式 UI 开发的核心工具之一。通过本文的源码分析,我们可以看到:
- 组件内 Builder 适合封装单页面内的 UI 片段,直接访问组件状态
- 全局 Builder 支持跨页面复用,通过
export导出 - 参数传递 让 Builder 具备数据驱动能力,支持自定义对象和基础类型
- 粒度控制 根据复杂度和复用范围,在 Builder 和子组件之间做合理选择
用好 @Builder,能让你的 ArkTS 代码保持清爽、模块化和可维护。
更多推荐



所有评论(0)