ArkTs学习之ArkTS装饰器@Builder装饰器,@LocalBuilder装饰器,@BuilderParam装饰器(二)
一、@Builder装饰器
@Builder是一种更轻量的 UI 元素复用机制,可以将重复使用的 UI 元素抽象成一个方法,并用@Builder修饰该方法,此方法就可以在 build() 里调用。
❗️注意:自定义组件的build()方法中,不允许调用没有被@Builder装饰的方法
1. 在组件内定义构建函数
构建函数是该组件的私有、特殊类型的成员函数,只能在组件内被访问。
构建函数中,this 指当前所属组件,组件的状态变量可以在构建函数内访问。
定义的语法:
@Builder MyBuilderFunction({ ... })
使用方法:
this.MyBuilderFunction({ ... })
示例:
@Entry @Component struct FirstComponent { @Builder MyBuilderFunction() { Text("我是在@builder里的text") .fontSize(30) } build() { //Column 是一个容器组件 Column() { //第一个行 Row() { this.MyBuilderFunction() //复用@Builder装饰的 MyBuilderFunction } //第二行 Row() { this.MyBuilderFunction(); //复用@Builder装饰的 MyBuilderFunction } } } }
2. 全局定义构建函数
全局的自定义构建函数可以被整个应用获取,不允许使用this和bind方法。
如果不涉及组件状态变化,建议使用全局定义构建方法。定义的语法:多了function关键字
@Builder function MyGlobalBuilderFunction({ ... })
使用方法:
MyGlobalBuilderFunction()
示例:
@Entry @Component struct FirstComponent { build() { //Column 是一个容器组件 Column() { //第一个行 Row() { MyGlobalBuilderFunction() //复用@Builder装饰的全局 MyGlobalBuilderFunction } //第二行 Row() { MyGlobalBuilderFunction(); //复用@Builder装饰的全局 MyGlobalBuilderFunction } } } } // 定义全局构建函数,需要添加function关键字 @Builder function MyGlobalBuilderFunction() { Text("我是在@builder里的Text").fontSize(30) }
3. 参数传递规则
自定义构建函数的参数传递有按值传递和按引用传递两种,均需遵守以下规则:
- 参数的类型必须与参数声明的类型一致,不允许undefined、null和返回undefined、null的表达式。
- 在自定义构建函数内部,不允许改变参数值。如果需要改变参数值,且同步回调用点,建议使用@Link。
- @Builder内UI语法遵循UI语法规则。
🌾 1、按引用传递参数
按引用传递参数时,传递的参数可为状态变量,且状态变量的改变会引起@Builder方法内的UI刷新。ArkUI提供$$作为按引用传递参数的范式。
@Builder function ABuilder($$: { paramA1: string }) { Row() { Text(`UseStateVarByReference: ${$$.paramA1} `) } } @Entry @Component struct Parent { @State label: string = 'Hello'; build() { Column() { // 在Parent组件中调用ABuilder的时候,将this.label引用传递给ABuilder ABuilder({ paramA1: this.label }) Button('Click me').onClick(() => { // 点击“Click me”后,UI从“Hello”刷新为“ArkUI” this.label = 'ArkUI'; }) } } }
🌾 2、按值传递参数
调用@Builder装饰的函数默认按值传递。当传递的参数为状态变量时,状态变量的改变不会引起@Builder方法内的UI刷新。所以当使用状态变量的时候,推荐使用按引用传递。
@Builder function ABuilder(paramA1: string) { Row() { Text(`UseStateVarByValue: ${paramA1} `) } } @Entry @Component struct Parent { label: string = 'Hello'; build() { Column() { ABuilder(this.label) } } }
二、@LocalBuilder装饰器
当开发者使用@Builder做引用数据传递时,会考虑组件的父子关系,使用了bind(this)之后,组件的父子关系和状态管理的父子关系并不一致。
为了解决组件的父子关系和状态管理的父子关系保持一致的问题,引入@LocalBuilder装饰器。
@LocalBuilder拥有和局部@Builder相同的功能,且比局部@Builder能够更好的确定组件的父子关系和状态管理的父子关系。
1. 在组件内定义构建函数
🌾 1、定义自定义组件
// 自定义组件 @Component export struct CommonView{ @State msg:string = "子 Child" @Builder customBuilder() {}; @BuilderParam customBuilderParam:()=>void=this.customBuilder build() { Column() { this.customBuilderParam() } } }
🌾2、使用该自定义组件
import { CommonView } from "../components/CommonView"; @Entity @Component export struct Main { /*成员变量*/ @State msg:string = "父Parent" /*构建函数*/ // 通过this.componentBuilder的形式传给子组件@BuilderParam customBuilderParam,this指向在Child的label,即“Child”。 @Builder customerBuilder(){ Text(this.msg) } // 过this.componentBuilder的形式传给子组件@BuilderParam customBuilderParam,this指向Parent的label,即“Parent”。 @LocalBuilder customerBuilder(){ Text(this.msg) } build() { Column() { Text('Main') CommonView({customBuilderParam:this.customerBuilder}) Text('End') }.width('100%').justifyContent(FlexAlign.Center) } }
使用@Builder装饰的效果图:
2. @LocalBuilder和@Builder区别说明
🔈说明:
@Builder componentBuilder()通过this.componentBuilder的形式传给子组件@BuilderParam customBuilderParam,this指向在Child的label,即“Child”。
@LocalBuilder componentBuilder()通过this.componentBuilder的形式传给子组件@BuilderParam customBuilderParam,this指向Parent的label,即“Parent”。
3. 参数传递规则
-
参数的类型必须与参数声明的类型一致,不允许undefined、null和返回undefined、null的表达式。
-
在@LocalBuilder修饰的函数内部,不允许改变参数值。
-
@LocalBuilder内UI语法遵循UI语法规则。
-
只有传入一个参数,且参数需要直接传入对象字面量才会按引用传递该参数,其余传递方式均为按值传递。
🌾 1、按值传递参数
当传递的参数为状态变量时,状态变量的改变不会引起@LocalBuilder方法内的UI刷新。所以当使用状态变量的时候,推荐使用按引用传递。
如下 点击事件 修改自定义组件参数内容后,自定义组件并不会刷新UI
import { ViewEntity } from "../animation/ViewEntity"; import { CommonView } from "../components/CommonView"; @Entity @Component export struct Main { /*成员变量*/ @State viewEntity:ViewEntity = new ViewEntity(); /*构建函数*/ @Builder customerBuilder(content:string){ Text(content) // 显示值传递的内 } build() { Column() { Text('Main') this.customerBuilder(this.viewEntity.content) // 使用自定义组件,传值字符串内容 Text('End') Button('修改数值').onClick(()=>{ this.viewEntity.content = "修改后的内容" }) }.width('100%').justifyContent(FlexAlign.Center) } }
🌾 2、按引用传递参数
按引用传递参数时,如果在@LocalBuilder方法内调用自定义组件,ArkUI提供$$作为按引用传递参数的范式。
组件Parent内的@LocalBuilder方法内调用自定义组件,且按照引用传递参数将值传递到自定义组件,
当Parent组件内状态变量值发生变化时,@LocalBuilder方法内的自定义组件HelloComponent的message值也会发生变化。
import { ViewEntity } from "../animation/ViewEntity"; import { CommonView } from "../components/CommonView"; @Entity @Component export struct Main { /*成员变量*/ @State viewEntity:ViewEntity = new ViewEntity(); /*构建函数*/ @Builder customerBuilder($$:ViewEntity){ Text($$.content) // 显示值传递的内 } build() { Column() { Text('Main') this.customerBuilder({content:this.viewEntity.content}) Text('End') Button('修改数值').onClick(()=>{ this.viewEntity.content = "修改后的内容" }) }.width('100%').justifyContent(FlexAlign.Center) } }
三、@BuilderParam装饰器
引用@Builder函数,@LocalBuilder函数
当开发者创建了自定义组件,并想对该组件添加特定功能时,例如在自定义组件中添加一个点击跳转操作。若直接在组件内嵌入事件方法,将会导致所有引入该自定义组件的地方均增加了该功能。为解决此问题,ArkUI引入了@BuilderParam装饰器,@BuilderParam用来装饰指向@Builder方法、@LocalBuilder方法的变量,开发者可在初始化自定义组件时对此属性进行赋值,为自定义组件增加特定的功能。该装饰器用于声明任意UI描述的一个元素,类似slot占位符。
@BuilderParam用来装饰指向@Builder方法的变量,可在初始化自定义组件时对此属性进行赋值。
@Entry @Component struct Parent { @Builder MyBuilderFunction() { Text("我是在@builder里的Text").fontSize(30) } build() { Column() { // 使用自定义组件Child,并用MyBuilderFunction初始化自组件的builder参数 Child({ builder: this.MyBuilderFunction }) } } } @Component struct Child { // 定义一个指向被@Builder修饰的方法的成员变量 // 并且要求这个被@Builder修饰的方法:没有入参,没有返回值 @BuilderParam builder: () => void; build() { Column() { this.builder() } } }
1. 初始化@BuilderParam装饰的方法
@BuildParam装饰的方法只能被自定义构建函数(@Builder装饰的方法)初始化。
使用所属自定义组件的自定义构建函数或者全局的自定义构建函数,在本地初始化@BuilderParam。
@Builder function GlobalBuilder() {} @Component struct Child { @Builder doNothtingBuilder() {} //在组件内定义构建函数 @BuilderParam aBuilder0: () => void = this.doNothtingBuilder() //全局定义构建函数 @BuilderParam aBuilder1: () => void = GlobalBuilder(); build() { this.aBuilder0() this.aBuilder1() } }
用父组件自定义构建函数初始化子组件@BuildParam装饰的方法。
@Component struct Child { @BuilderParam aBuilder0: () => void; build() { Column() { this.aBuilder0() } } } @Entry @Component struct Parent { @Builder componentBuilder() { Text(`Parent builder `) } build() { Column() { Child({ aBuilder0: this.componentBuilder }) } } }
需注意this指向正确。
以下示例中,Parent组件在调用this.componentBuilder()时,this.label指向其所属组件,即“Parent”。@Builder componentBuilder()传给子组件@BuilderParam aBuilder0,在Child组件中调用
this.aBuilder0()时,this.label指向在Child的label,即“Child”。
开发者谨慎使用bind改变函数调用的上下文,可能会使this指向混乱。
@Component struct Child { label: string = `Child` @BuilderParam aBuilder0: () => void; build() { Column() { this.aBuilder0() } } } @Entry @Component struct Parent { label: string = `Parent` @Builder componentBuilder() { Text(`${this.label}`) } build() { Column() { this.componentBuilder() Child({ aBuilder0: this.componentBuilder }) } } }
2. 使用场景
🌾 1. 参数初始化组件
@BuilderParam装饰的方法可以是有参数和无参数的两种形式,需与指向的@Builder方法类型匹配。@BuilderParam装饰的方法类型需要和@Builder方法类型一致。
@Builder function GlobalBuilder1($$: { label: string }) { Text($$.label) .width(400) .height(50) .backgroundColor(Color.Blue) } @Component struct Child { label: string = 'Child' // 无参数类,指向的componentBuilder也是无参数类型 @BuilderParam aBuilder0: () => void; // 有参数类型,指向的GlobalBuilder1也是有参数类型的方法 @BuilderParam aBuilder1: ($$: { label: string }) => void; build() { Column() { this.aBuilder0() this.aBuilder1({ label: 'global Builder label' }) } } } @Entry @Component struct Parent { label: string = 'Parent' @Builder componentBuilder() { Text(`${this.label}`) } build() { Column() { this.componentBuilder() Child({ aBuilder0: this.componentBuilder, aBuilder1: GlobalBuilder1 }) } } }
🌾 2. 尾随闭包初始化组件
在自定义组件中使用@BuilderParam装饰的属性时也可通过尾随闭包进行初始化。在初始化自定义组件时,组件后紧跟一个大括号“{}”形成尾随闭包场景。
此场景下自定义组件内有且仅有一个使用@BuilderParam装饰的属性。
开发者可以将尾随闭包内的内容看做@Builder装饰的函数传给@BuilderParam。示例如下:
// xxx.ets @Component struct CustomContainer { @Prop header: string; @BuilderParam closer: () => void build() { Column() { Text(this.header) .fontSize(30) this.closer() } } } @Builder function specificParam(label1: string, label2: string) { Column() { Text(label1) .fontSize(30) Text(label2) .fontSize(30) } } @Entry @Component struct CustomContainerUser { @State text: string = 'header'; build() { Column() { // 创建CustomContainer,在创建CustomContainer时,通过其后紧跟一个大括号“{}”形成尾随闭包 // 作为传递给子组件CustomContainer @BuilderParam closer: () => void的参数 CustomContainer({ header: this.text }) { Column() { specificParam('testA', 'testB') }.backgroundColor(Color.Yellow) .onClick(() => { this.text = 'changeHeader'; }) } } } }