mutableBuilder:实现全局@Builder动态更新
当在一个自定义组件内使用多个全局@Builder函数实现UI的不同效果时,代码维护将变得非常困难,且页面不够整洁。此时,可以使用wrapBuilder封装全局@Builder。但是wrapBuilder不支持动态切换@Builder,引入mutableBuilder实现全局@Builder的动态切换。
从API version 22开始,开发者可以使用mutableBuilder实现全局@Builder的动态切换。
从API version 22开始,mutableBuilder支持在元服务中使用。
wrapBuilder不支持动态全局@Builder
当前wrapBuilder不支持二次赋值, 更改@Builder,UI不会发生变化。
class TextContent {
text: string = '';
}
@Builder
function textBuilder(p: TextContent) {
Text(p.text)
}
@Builder
function buttonBuilder(p: TextContent) {
Button(p.text)
}
@Entry
@Component
struct Index {
@State message: string = 'init';
@State text: WrappedBuilder<[TextContent]> = wrapBuilder(textBuilder); // 使用textBuilder初始化
build() {
Column() {
this.text.builder({ text: this.message })
Button().onClick(() => {
this.text = wrapBuilder(buttonBuilder); // 点击Button,将textBuilder替换为buttonBuilder进行二次赋值
})
}
}
}
在上述代码中,使用textBuilder初始化wrapBuilder,点击Button的onClick事件,使用buttonBuilder再次初始化wrapBuilder,不会触发对应的@Builder的更新。
为了解决这一问题,引入mutableBuilder作为动态全局@Builder封装函数。mutableBuilder返回MutableBuilder对象,用于全局@Builder的动态刷新。
接口说明
mutableBuilder是一个模板函数,返回一个MutableBuilder对象。相比WrappedBuilder,MutableBuilder可以实现动态切换全局@Builder。
declare function mutableBuilder<Args extends Object[]>(builder: BuilderCallback): MutableBuilder<Args>;
同时MutableBuilder对象是一个模板类,继承自WrappedBuilder。
declare class MutableBuilder<Args extends Object[]> extends WrappedBuilder<Args> {
}
模板参数Args extends Object[]需要匹配@Builder函数参数的类型。
使用方法:
let builderVar: MutableBuilder<[string, number]> = mutableBuilder(MyBuilder);
let builderArr: MutableBuilder<[string, number]>[] = [mutableBuilder(MyBuilder)]; // mutableBuilder支持放入数组中
限制条件
-
mutableBuilder方法只能传入全局@Builder方法,传入局部@Builder方法编译时报错。
class TextContent {text: string = '';}@Builderfunction globalBuilder(p: TextContent) {Text(p.text)}@ComponentV2struct MyApp {@Local message: string = 'init';// 正确用法,使用全局@Builder@Local switchingBuilder: MutableBuilder<[TextContent]> = mutableBuilder(globalBuilder);// 错误用法,使用局部@Builder,编译报错@Local localBuilderObject: MutableBuilder<[TextContent]> = mutableBuilder(this.localBuilder);@BuilderlocalBuilder(p: TextContent) {Text(p.text)}build() {Column() {this.switchingBuilder.builder({ text: this.message })}}} -
MutableBuilder对象的builder属性方法仅限在自定义组件内部使用,在自定义组件外面使用会导致程序运行时崩溃。
class TextContent {text: string = '';}@Builderfunction globalBuilder(p: TextContent) {Text(p.text)}// 错误用法,MutableBuilder对象的builder属性方法在自定义组件外面使用,运行时崩溃let outSideBuilder: MutableBuilder<[TextContent]> = mutableBuilder(globalBuilder);outSideBuilder.builder({ text: 'message' });@ComponentV2struct MyApp {@Local message: string = 'init';@Local switchingBuilder: MutableBuilder<[TextContent]> = mutableBuilder(globalBuilder);build() {Column() {// 正确用法,MutableBuilder对象的builder属性方法在自定义组件中使用this.switchingBuilder.builder({ text: this.message })}}} -
不建议与wrapBuilder混合使用,因为mutableBuilder创建的对象类型是MutableBuilder类型,会导致不符合预期的更新。
如下为不推荐的用法:
// 在实例化MutableBuilder对象时,建议使用mutableBuilder(builderName)方法@State switchingBuilder: MutableBuilder<[MutableBinding]> = mutableBuilder(textBuilder);// 不支持将MutableBuilder类型的变量赋值为undefined或null,会导致运行时crash@State switchingBuilder: MutableBuilder<[MutableBinding]> | undefined | null = null;Button(`MutableBuilder`).onClick(() => {// 不建议将wrapBuilder创建的对象赋值给MutableBuilder类型的对象,赋值后会将textBuilder动态切换成buttonBuilderthis.switchingBuilder = wrapBuilder(buttonBuilder);})如下为推荐用法:
// 在实例化MutableBuilder对象时,建议使用mutableBuilder(builderName)方法@State switchingBuilder: MutableBuilder<[MutableBinding]> = mutableBuilder(textBuilder);Button(`MutableBuilder`).onClick(() => {// 赋值会将wrapBuilder中textBuilder中动态切换成buttonBuilderthis.switchingBuilder = mutableBuilder(buttonBuilder); // 推荐用法})
动态更改全局@Builder实例
使用@Builder装饰器装饰的方法textBuilder作为mutableBuilder的参数,然后将mutableBuilder的返回值赋值给变量switchingBuilder,在Button的点击事件中,使用@Builder装饰器装饰的方法buttonBuilder作为mutableBuilder的参数,将mutableBuilder的返回值再次赋值给变量switchingBuilder,可实现textBuilder 更新为buttonBuilder,以解决wrapBuilder不支持二次赋值的问题。
class TextContent {
text: string = '';
}
@Builder
function textBuilder(p: TextContent) {
Text(p.text).margin(20)
}
@Builder
function buttonBuilder(p: TextContent) {
Button(p.text).margin(20)
}
let counter: number = 1;
@Entry
@ComponentV2
struct MyApp {
@Local message: string = 'init';
@Local switchingBuilder: MutableBuilder<[TextContent]> = mutableBuilder(textBuilder);
build() {
Column() {
this.switchingBuilder.builder({ text: this.message })
Button('Click to change')
.onClick(() => {
counter++; // 每次点击按钮修改counter来动态改变全局@Builder
if(counter % 2 === 0) {
this.message += 'B';
this.switchingBuilder = mutableBuilder(buttonBuilder); // textBuilder--->buttonBuilder
} else {
this.message += 'T';
this.switchingBuilder = mutableBuilder(textBuilder); // buttonBuilder--->textBuilder
}
})
}.position({x: 120, y: 60})
}
}
点击Button,可将textBuilder动态更改为buttonBuilder,如下图所示:

使用mutableBuilder显示弹出菜单
由于MutableBuilder继承自WrappedBuilder,故mutableBuilder对应的@Builder具有跟WrappedBuilder同等能力,如下示例,mutableBuilder对应的@Builder方法可作为bindMenu入参,支持点击弹出菜单。
@Builder
function overBuilder() {
Row() {
Text('全局 Builder')
.fontSize(30)
.fontWeight(FontWeight.Bold)
}
}
@Entry
@Component
struct Index {
@State arr: number[] = [1,2,3,4,5];
mutableBuilderMenu: MutableBuilder<[]> = mutableBuilder<[]>(overBuilder);
build() {
Column() {
List({ space: 10 }) {
ForEach(this.arr, (item: number) => {
ListItem() {
Text(`${item}`)
.width('100%')
.height(100)
.fontSize(16)
.textAlign(TextAlign.Center)
.borderRadius(10)
.backgroundColor(0xFFFFFF)
}
.bindMenu(this.mutableBuilderMenu.builder)
}, (item: number) => JSON.stringify(item))
}
}
}
}
观察mutableBuilder中@Builder的变化
mutableBuilder对应的@Builder函数中可使用MutableBinding进行包裹来观察状态变量的变化,同时可通过@Monitor或addMonitor监听mutableBuilder中@Builder的变化。
import { UIUtils, MutableBinding } from '@kit.ArkUI';
@Builder
function textBuilder(p: MutableBinding<string>) {
Text(p.value)
.margin(20)
.onClick(() => {
p.value += 't';
})
}
@Builder
function buttonBuilder(p: MutableBinding<string>) {
Button(p.value)
.margin(20)
.onClick(() => {
p.value += 'b';
})
}
let counter: number = 1;
@Entry
@ComponentV2
struct MyApp {
@Local message: string = 'init';
@Local switchingBuilder: MutableBuilder<[MutableBinding<string>]> = mutableBuilder(textBuilder);
@Monitor('switchingBuilder') variableChange(m: IMonitor): void {
console.info(`Builder changed. is buttonBuilder: ${m.value<MutableBuilder<[MutableBinding<string>]>>()?.now.builder === buttonBuilder}`);
}
build() {
Column() {
this.switchingBuilder.builder(UIUtils.makeBinding(()=> this.message, txt => this.message = txt))
Button('Click to change')
.onClick(() => {
counter++;
if(counter % 2 === 0) {
this.message += 'B';
this.switchingBuilder = mutableBuilder(buttonBuilder); // textBuilder--->buttonBuilder,@Monitor会触发回调
} else {
this.message += 'T';
this.switchingBuilder = mutableBuilder(textBuilder); // buttonBuilder--->textBuilder,@Monitor会触发回调
}
})
}.position({x: 120, y: 60})
}
}
点击Button,可将textBuilder动态切换为buttonBuilder。点击buttonBuilder,this.message会自动加B,如下图所示:

点击Button将textBuilder动态切换为buttonBuilder时,@Monitor会监听到全局@Builder的变化,并打印日志@Builder change. is buttonBuilder: true。