跳到主要内容

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支持放入数组中

限制条件

  1. mutableBuilder方法只能传入全局@Builder方法,传入局部@Builder方法编译时报错。

    class TextContent {
    text: string = '';
    }

    @Builder
    function globalBuilder(p: TextContent) {
    Text(p.text)
    }

    @ComponentV2
    struct MyApp {
    @Local message: string = 'init';
    // 正确用法,使用全局@Builder
    @Local switchingBuilder: MutableBuilder<[TextContent]> = mutableBuilder(globalBuilder);
    // 错误用法,使用局部@Builder,编译报错
    @Local localBuilderObject: MutableBuilder<[TextContent]> = mutableBuilder(this.localBuilder);

    @Builder
    localBuilder(p: TextContent) {
    Text(p.text)
    }
    build() {
    Column() {
    this.switchingBuilder.builder({ text: this.message })
    }
    }
    }
  2. MutableBuilder对象的builder属性方法仅限在自定义组件内部使用,在自定义组件外面使用会导致程序运行时崩溃。

    class TextContent {
    text: string = '';
    }

    @Builder
    function globalBuilder(p: TextContent) {
    Text(p.text)
    }

    // 错误用法,MutableBuilder对象的builder属性方法在自定义组件外面使用,运行时崩溃
    let outSideBuilder: MutableBuilder<[TextContent]> = mutableBuilder(globalBuilder);
    outSideBuilder.builder({ text: 'message' });

    @ComponentV2
    struct MyApp {
    @Local message: string = 'init';
    @Local switchingBuilder: MutableBuilder<[TextContent]> = mutableBuilder(globalBuilder);
    build() {
    Column() {
    // 正确用法,MutableBuilder对象的builder属性方法在自定义组件中使用
    this.switchingBuilder.builder({ text: this.message })
    }
    }
    }
  3. 不建议与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动态切换成buttonBuilder
    this.switchingBuilder = wrapBuilder(buttonBuilder);
    })

    如下为推荐用法:

    // 在实例化MutableBuilder对象时,建议使用mutableBuilder(builderName)方法
    @State switchingBuilder: MutableBuilder<[MutableBinding]> = mutableBuilder(textBuilder);

    Button(`MutableBuilder`).onClick(() => {
    // 赋值会将wrapBuilder中textBuilder中动态切换成buttonBuilder
    this.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进行包裹来观察状态变量的变化,同时可通过@MonitoraddMonitor监听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。