鸿蒙应用开发UI基础第二十六节:轻量级UI元素@Builder与@LocalBuilder区别示例演示
本文摘要: ArkTS中@Builder是一种轻量级UI复用方案,用于封装纯UI结构片段,无实例、状态和生命周期。与@Component相比,@Builder更适用于无业务逻辑的UI复用场景。文章详细介绍了@Builder的两种定义方式(组件内和全局)、参数传递规则(按值/按引用),以及高级用法如嵌套构建、this指向和@BuilderParam插槽机制。通过多个代码示例展示了不同传参方式对UI刷
【学习目标】
- 理解 @Builder 设计初衷,明确与 @Component 核心差异;
- 掌握 @Builder 两种定义方式、参数传递(按值/按引用)规则;
- 掌握 @Builder 高级场景(嵌套、this指向)实战用法;
- 掌握 @BuilderParam UI 插槽原理与使用方法,实现组件 UI 内容灵活定制;
- 掌握 @LocalBuilder 用法、参数传递与限制,理解其固定this、维持组件父子关系的核心特性;
- 能够清晰对比 @Builder 与 @LocalBuilder,在实际开发中正确选型。
一、Builder 核心认知
1.1 什么是 @Builder
@Builder 用于封装一段UI结构,仅负责UI渲染,无独立实例、无状态、无生命周期,不会加入组件树,编译阶段会直接内联展开,是组件内/全局复用UI片段的最优轻量方案。
1.2 为什么需要 @Builder
我们已经掌握 @Component 封装可复用UI,适用于独立功能、带业务逻辑、带生命周期的页面/组件。
但开发中存在大量纯UI结构、无状态、无业务、无生命周期的复用片段例如 列表内固定骨架图,页面内重复UI区块。
这类片段不需要独立实例、状态、生命周期,使用 @Component 会造成额外实例开销,粒度过细。因此 ArkTS 提供最轻量UI复用方案:@Builder 自定义构建函数,专注细粒度UI复用,几乎无额外性能消耗。
1.3 @Builder 与 @Component 核心区别
| 特性 | @Builder | @Component |
|---|---|---|
| 本质 | UI构建函数 | 独立自定义组件 |
| 实例 | 无实例 | 有独立组件实例 |
| 状态支持 | 不能声明@State | 支持@State、@Prop等状态 |
| 生命周期 | 无生命周期 | 拥有完整生命周期 |
| 调用方式 | 直接调用 | 作为组件使用 |
| 适用场景 | 纯UI结构复用 | 功能组件/页面/业务模块 |
| 刷新范围 | 随所属组件整体刷新,无独立刷新 | 可独立响应式刷新 |
| 调用位置 | 只能在build()内部使用 | 任意生命周期与场景均可使用 |
1.4 @Builder 两种定义形式
- 组件内 @Builder:仅当前组件内使用,可直接访问组件成员变量与状态,跟随组件刷新而刷新;
- 全局 @Builder:整个工程通用,不依赖任何组件实例,不能访问this,所有数据必须通过参数传递。
二、项目工程
本节工程BuilderDemo基于 API 20+,所有案例页面完整对应如下:
BuilderDemo/
├── AppScope/
├── entry/
│ ├── src/main/ets
│ │ ├── entryability/EntryAbility.ets
│ │ ├── pages/
│ │ │ ├── Index.ets # 入口:页面跳转
│ │ │ ├── InnerBuilderPage.ets # 组件内Builder
│ │ │ ├── GlobalBuilderPage.ets # 全局Builder
│ │ │ ├── BuilderParamPage.ets # @BuilderParam UI插槽
│ │ │ ├── AdvancedBuilderPage.ets # 高级:嵌套、this指向、Binding
│ │ │ └── LocalBuilderPage.ets # @LocalBuilder 专属案例
三、Index.ets 入口页面
import router from '@ohos.router';
// 定义页面配置接口
interface PageConfig {
title: string;
url: string;
}
@Entry
@Component
struct Index {
pageList: PageConfig[] = [
{ title: "1. 组件内 Builder", url: "pages/InnerBuilderPage" },
{ title: "2. 全局 Builder", url: "pages/GlobalBuilderPage" },
{ title: "3. BuilderParam 插槽", url: "pages/BuilderParamPage" },
{ title: "4. this指向问题", url: "pages/AdvancedBuilderPage" },
{ title: "5. @LocalBuilder", url: "pages/LocalBuilderPage" }
];
// 自定义Builder按钮封装
@Builder builderButton(title: string, onClick: () => void) {
Button(title)
.width(280)
.onClick(onClick);
}
build() {
Column({ space: 20 }) {
Text("@Builder & @LocalBuilder 实战")
.fontSize(26)
.fontWeight(FontWeight.Bold);
// ForEach 循环渲染按钮
ForEach(
this.pageList,
(item: PageConfig) => {
this.builderButton(item.title, () => {
router.pushUrl({ url: item.url });
});
}
);
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center);
}
}
四、组件内 @Builder 示例演示
4.1 规则说明
- 组件内 @Builder 直接访问 this 状态变量时,会跟随组件整体刷新,不受传参规则限制,自动刷新;
- @Builder 默认按值传递参数,传递基础类型、对象变量、多参数时,外部状态更新不会触发 @Builder 刷新;
- @Builder 实现响应式刷新的标准方式:使用对象字面量形式传入参数,可建立响应式关联,状态更新自动刷新;
- 多层 @Builder 嵌套时,每一层都必须使用对象字面量传参,才能保证全链路刷新;
- 禁止在 @Builder 内部修改传入的参数对象,会导致运行异常;
- 从 API version 20 开始,可通过 UIUtils.makeBinding()、Binding / MutableBinding 实现 @Builder 内状态变量刷新;
- 使用 UIUtils.makeBinding() 包装读状态回调,支持 @Builder 内 UI 自动刷新;
- 额外传入写状态回调,可将 @Builder 内部的参数修改同步到外部组件。
4.2 说明
@Builder被用来展示组件,不会参与动态UI刷新。组件中值的变化是通过使用装饰器的特性,监听到值的改变触发的UI刷新,而不是通过@Builder的能力触发的。
4.3 示例代码
示例 1:无参 @Builder + 直接访问 this → 自动刷新
class UserInfo {
name: string = ''
age: number = 0
}
@Entry
@Component
struct InnerBuilderPage {
@State message: string = '组件内 @Builder 实战'
@State count: number = 0
@State user: UserInfo = {name:"Tom", age:25}
@Builder
buildTitle() {
Text(this.message)
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
}
build() {
Column({ space: 15 }) {
this.buildTitle()
Button('更新标题')
.onClick(() => {
this.message = "组件内 @Builder 实战-更新"
})
}
.width('100%')
}
}
示例 2:按值传递 → 不刷新
@Builder
buildButton(label: string) {
Text("基础类型传参 → 不刷新:" + label)
}
// 调用
this.buildButton(`count:${this.count}`)
示例 3:直接传递对象变量(按值传递) → 不刷新
@Builder
buildUserCardByObj(params: UserInfo) {
Text(`姓名:${params.name} 年龄:${params.age}`)
}
// 调用
this.buildUserCardByObj(this.user)
示例 4:多参数传递(按值传递) → 不刷新
@Builder
buildUserCardByValue(name:string, age:number) {
Text(`姓名:${name} 年龄:${age}`)
}
// 调用
this.buildUserCardByValue(this.user.name, this.user.age)
示例 5:对象字面量 → 按引用传递 → 刷新
@Builder
buildUserCardByRef(params: UserInfo) {
Text(`姓名:${params.name} 年龄:${params.age}`)
}
// 调用
this.buildUserCardByRef({ name: this.user.name, age: this.user.age })
示例 6:嵌套 @Builder + 子组件刷新对比
@Component
struct ChildComponent1 {
@Prop info: UserInfo = new UserInfo();
build() {
Row() {
Text(`ChildComponent1不刷新===${this.info.name}===${this.info.age}`)
.fontSize(20)
.fontWeight(FontWeight.Bold)
}
}
}
@Component
struct ChildComponent2 {
@Prop childName: string = '';
@Prop childAge: number = 0;
build() {
Row() {
Text(`ChildComponent2=刷新==${this.childName}===${this.childAge}`)
.fontSize(20)
.fontWeight(FontWeight.Bold)
}
}
}
@Builder
parentBuilder(params: UserInfo) {
Text(`parentBuilder刷新===${params.name}===${params.age}`)
.fontSize(20)
.fontWeight(FontWeight.Bold)
ChildComponent1({ info: params }) // 不刷新
ChildComponent2({ childName: params.name, childAge: params.age }) // 刷新
}
示例 7:错误示范:Builder 内部修改参数
@Builder
errorModifyParam(params: UserInfo) {
Button("修改")
.onClick(() => {
params.name = "错误修改" // 运行异常
})
}
示例 8:API20 新特性 → Binding 绑定传参(可修改+可刷新)
@Builder
ModifyParam(params: Binding<UserInfo>) {
Text(`姓名:${params.value.name}`)
Button("内部修改").onClick(() => {
params.value.name = "新名字"
})
}
// 调用
this.ModifyParam(UIUtils.makeBinding(
() => this.user,
(user) => { this.user = user }
))
运行结果

五、全局 @Builder 实战
5.1 核心功能
- 定义全局通用UI片段,多页面共享;
- 无组件依赖,不绑定this,所有数据必须通过参数传递。
5.2 规则
- 必须加
function关键字 - 跨页面使用需要加
export
5.3 示例代码
interface GlobalInfo {
globalContent:string
callback?: () => void
}
@Builder
export function globalButton(globalInfo: GlobalInfo) {
Button(globalInfo.globalContent)
.width(180)
.height(46)
.backgroundColor('#34C85E')
.margin(5)
.onClick(globalInfo?.callback)
}
@Builder
export function globalCard(title: string, content: string) {
Column() {
Text(title).fontSize(20).fontWeight(FontWeight.Bold)
Text(content).fontSize(16).margin({ top: 10 })
}
.width('100%')
.padding(20)
.backgroundColor('#fff')
.borderRadius(12)
.margin(10)
}
@Entry
@Component
struct GlobalBuilderPage {
@State content: string = '全局复用,跨页面统一样式'
@State globalInfo: GlobalInfo = {globalContent:'全局按钮1'}
build() {
Column({ space: 20 }) {
globalCard('全局 @Builder 实战', this.content)
// 只能通过引用传递 一一传递对应参数 否则无法刷新。
globalButton({
globalContent:this.globalInfo.globalContent,
callback:()=>{
this.globalInfo.globalContent = '全局按钮点击更新'
}
})
}
.width('100%')
.padding(20)
}
}
5.4 运行结果

六、@BuilderParam UI插槽
6.1 核心功能
@BuilderParam 用于接收外部传入的 @Builder 函数,实现UI插槽(slot) 能力,让组件结构固定、内容可灵活定制。
6.2 说明
@BuilderParam 是组件属性装饰器,专门用于接收 @Builder 函数类型的参数。
使用场景:父组件向子组件传递一段UI结构,子组件只负责布局,不关心具体内容。
6.3 示例代码
@Component
struct CardComponent {
@BuilderParam header?: () => void
@BuilderParam content?: () => void
build() {
Column() {
if (this.header){
this.header()
}
Divider().margin(10).color(Color.Red).strokeWidth(2)
if (this.content){
this.content()
}
}
.width('100%')
.backgroundColor('#fff')
.padding(20)
.borderRadius(12)
}
}
@Entry
@Component
struct BuilderParamPage {
@Builder
customHeader() {
Text('自定义头部').fontSize(22).fontWeight(FontWeight.Bold)
}
@Builder
customContent() {
Text('这是插槽内容,灵活传入任意UI')
Button('插槽内按钮').width(150).margin(10)
}
build() {
Column({ space: 20 }) {
CardComponent({
header: this.customHeader,
content:this.customContent
})
}
.padding(20)
}
}
6.4 运行结果

七、关于this 指向(重点)
我们在使用 @Builder + @BuilderParam 跨组件传递时,会出现一个非常关键的问题:this 指向会变导致UI刷新不符合预期。
7.1 this 指向核心规则
- @Builder 直接作为引用传递时,this 由「调用它的组件」决定。
- 箭头函数没有自己的 this,会继承外层作用域的 this。
- 在
@BuilderParam场景:- 直接传递:
customBuilderParam: this.componentBuilder
→ 函数在 Child 里执行,this 指向 Child。 - 箭头函数包裹:
customBuilderParam: () => { this.componentBuilder() }
→ 继承父组件 this,this 指向 Parent。
- 直接传递:
7.2 示例代码
/**
* 问题:@Builder + @BuilderParam this 指向问题
* 页面运行结果:Parent Child Parent
* 核心规则:
* 1. 直接传递函数引用 → this 由【调用者】决定
* 2. 箭头函数包裹 → this 继承【定义时的组件】
*/
@Component
struct ChildComponent {
label: string = "Child";
// 默认占位Builder,无实际UI
@Builder
customBuilder() {}
@Builder
customChangeThisBuilder() {}
// 接收外部传入的Builder函数
@BuilderParam customBuilderParam: () => void = this.customBuilder;
@BuilderParam customChangeThisBuilderParam: () => void = this.customChangeThisBuilder;
build() {
Row({ space: 15 }) {
// 执行外部传入的构建函数
this.customBuilderParam()
this.customChangeThisBuilderParam()
}
}
}
@Entry
@Component
struct AdvancedBuilderPage {
label: string = "Parent";
// 普通 @Builder:this 由【运行时调用者】决定,不固定!
@Builder
componentBuilder() {
Text(`${this.label}`)
.fontSize(26)
.fontWeight(FontWeight.Bold)
}
build() {
Column({ space: 20 }) {
Text("运行结果:").fontSize(22)
Row({ space: 15 }) {
// 1️⃣ 父组件自己调用 → this 指向Parent → 显示 Parent
this.componentBuilder()
ChildComponent({
// 2️⃣ 直接传递函数引用:函数在子组件内执行 → this 指向Child → 显示 Child
customBuilderParam: this.componentBuilder,
// 3️⃣ 箭头函数包裹:继承父组件this → this 固定指向Parent → 显示 Parent
customChangeThisBuilderParam: () => { this.componentBuilder() }
})
}
}
.width('100%')
.padding(30)
}
}
7.3 运行结果
最终页面显示:Parent Child Parent

7.4 一句话总结
this 是函数执行时的上下文对象,它的指向不是在函数定义时确定的,而是在函数调用时才最终确定。
- 普通函数:谁调用,this 就指向谁(默认规则,也是最容易错乱的场景);
- 箭头函数:没有自己的 this,继承外层作用域的 this(永远不会错乱,优先推荐);
八、@LocalBuilder 装饰器
@Builder 在跨组件传递时,this 会被改变,这会导致严重问题UI 刷新不符合预期,为了彻底解决这个问题,ArkTS提供了 @LocalBuilder。
@LocalBuilder可以固定this指向当前
8.1 设计目的
@LocalBuilder 拥有和局部 @Builder 完全一样的UI构建能力,但可以:
- 固定 this 指向
- 保证组件父子关系不变
- 保证状态管理父子关系不变
从根源解决 @Builder 的上下文错乱问题。
8.2 核心特性
- 只能在组件内部定义,不支持全局;
- this 永远指向声明它的组件,不会被任何方式改变;
- 跨组件传递时,组件归属固定,不会被修改;
- 调用方式与 @Builder 完全一致:
this.xxx()。
8.3 @LocalBuilder与@Builder对比示例
@Component
struct Child {
label: string = 'Child';
@BuilderParam customBuilderParam?: () => void;
build() {
if (this.customBuilderParam){
this.customBuilderParam()
}else {
Text('@BuilderParam undefined' )
}
}
}
@Entry
@Component
struct LocalBuilderPage {
label: string = 'Parent';
// @Builder:this 指向调用方 → Child
@Builder
componentBuilder() {
Text(this.label)
}
// @LocalBuilder:this 永远指向 Parent
@LocalBuilder
componentLocalBuilder() {
Text(this.label)
}
build() {
Column({ space: 20 }) {
// 显示 Child
Child({ customBuilderParam: this.componentBuilder })
// 显示 Parent
Child({ customBuilderParam: this.componentLocalBuilder })
}
}
}
8.4 限制条件
- 只能在组件内声明,不允许全局;
- 不能与其他任何装饰器一起使用;
- 不能修饰静态方法;
- 函数内部不允许修改参数;
- 作为参数传递时推荐:直接传函数:
this.func或箭头函数:() => { this.func() }this指向都不会变. - 禁止直接传执行结果,会导致布局错乱。
8.5 参数传递规则(与 @Builder 一致)
- 按值传递:基础类型、多参数、直接对象 → 不刷新
- 按引用传递:单个对象字面量 → 可刷新
- 按回调传递:API20 使用 UIUtils.makeBinding → 可刷新可修改
注意:子组件调用父组件的 @LocalBuilder 并传参时,子组件状态变化不会触发 @LocalBuilder 刷新;推荐:@LocalBuilder 内直接访问父组件自身状态。
九、代码仓库
- 工程名称:BuilderDemo
- 仓库地址:https://gitee.com/HarmonyOS-UI-Basics/harmony-os-ui-basics.git
十、下节预告
下一节我们将深入学习鸿蒙UI样式复用三大核心方案:
- 掌握 @Styles 通用样式复用,实现多组件基础样式统一;
- 掌握 @Extend 组件专属样式扩展,支持参数与私有属性;
- 掌握 stateStyles 多态状态样式,实现按压/禁用/选中自动切换;
- 清晰区分三者适用场景,写出简洁、可维护、高性能样式代码;
更多推荐




所有评论(0)