HarmonyOS NEXT开发进阶(四):@Builder 装饰器实现UI结构复用

发布于:2024-12-18 ⋅ 阅读:(70) ⋅ 点赞:(0)

一、前言

当页面有多个相同的UI结构时,若每个都单独声明,同样会有大量重复的代码。为避免重复代码,可以将相同的UI结构提炼为一个自定义组件,完成UI结构的复用。

除此之外,ArkTS还提供了一种更轻量的UI结构复用机制@Builder方法,开发者可以将重复使用的UI元素抽象成一个@Builder方法,该方法可在build()方法中调用多次,以完成UI结构的复用。

二、自定义构建函数

ArkUI提供了一种更轻量的UI元素复用机制@Builder@Builder所装饰的函数遵循build()函数语法规则,开发者可以将重复使用的UI元素抽象成一个方法,在build方法里调用。

为简化描述,全文将@Builder装饰的函数也称为“自定义构建函数”。

2.1 自定义组件内自定义构建函数

组件内声明,使用时需要用this

定义语法:

@Builder MyBuilderFunction(){}

使用方法:

this.MyBuilderFunction()

@Builder 自定义组件内自定义构建函数使用注意事项如下:

  1. 允许在自定义组件内定义一个或多个@Builder方法,该方法被认为是该组件的私有、特殊类型的成员函数。
  2. 自定义构建函数可以在所属组件的build方法和其他自定义构建函数中调用,但不允许在组件外调用。
  3. 在自定义函数体中,this指代当前所属组件,组件的状态变量可以在自定义构建函数内访问。建议通过this访问自定义组件的状态变量而不是参数传递。

2.2 全局自定义构建函数

全局声明需要加function关键字,使用时直接写函数名称。

定义语法:

@Builder function MyGlobalBuilderFunction(){}

使用方法:

MyGlobalBuilderFunction()

@Builder 全局自定义构建函数使用注意事项如下:

  1. 全局的自定义构建函数可以被整个应用获取,不允许使用thisbind方法。
  2. 如果不涉及组件状态变化,建议使用全局的自定义构建方法。

2.3 参数传递规则

自定义构建函数的参数传递有按值传递按引用传递两种,均需遵守以下规则:

  • 参数的类型必须与参数声明的类型一致,不允许undefinednull和返回undefinednull的表达式。
  • 在自定义构建函数内部,不允许改变参数值。如果需要改变参数值,且同步回调用点,建议使用@Link
  • @Builder内UI语法遵循UI语法规则。
  • 只有传入一个参数,且参数需要直接传入对象字面量才会按引用传递该参数,其余传递方式均为按值传递。
2.3.1 按值传递参数
@Builder 
function overBuilder(paramA1: string) {
  Row() {
    Text(`UseStateVarByValue: ${paramA1} `)
  }
}
@Entry
@Component
struct Parent {
  @State label: string = 'Hello';
  build() {
    Column() {
      overBuilder(this.label)
    }
  }
}
2.3.2 按引用传递参数

按引用传递参数时,传递的参数可为状态变量,且状态变量的改变会引起@Builder方法内的UI刷新。ArkUI提供$$作为按引用传递参数的范式。

使用语法如下:

@Builder( $$ : { paramA1: string, paramB1 : string } );

应用示例如下:

@Builder function ABuilder($$:{par: string}){
  Row(){
    Text( 'abs:' + $$.par)
  }
}
@Entry
@Component
struct Index {
  @State message: string = 'Hello World'
  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
        ABuilder({par:this.message})
        Button('Click Me')
          .onClick(() => {
            this.message = "哈哈"
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

或者按照如下方式实现:

@Builder
function ListModel(params:{title:string,count:number}){
   Row(){
     Text(`${params.title}`)
       .height(40)
       .textAlign(TextAlign.Center)
       .fontColor(Color.White)

     Text(`投票人数:${params.count}`)
       .fontColor(Color.White)
   }
   .width("100%")
   .height(100)
   .backgroundColor(Color.Brown)
    .justifyContent(FlexAlign.SpaceAround)
}

@Entry
@Component
struct Index {
  @State CountMusic:number=10
  build() {
      Column({space:10}) {
        ListModel({title:"音乐",count:this.CountMusic})
        ListModel({title:"美术",count:30})
        ListModel({title:"舞蹈",count:12})

        Button("增加音乐支持人数").onClick(()=>{
          this.CountMusic++;
        })
      }.width('100%')
      .height('100%')
  }
}

2.4 @Builder方法和自定义组件的区别

  • @Builder方法和自定义组件虽然都可以实现UI复用的效果,但是两者还是有着本质区别,其中最为显著的一个区别就是自定义组件可以定义自己的状态变量,而@Builder方法则不能。

  • 若复用的UI结构没有状态,推荐使用@Builder方法,否则使用自定义组件。

  • 在使用 @Builder 复用逻辑时,可以支持传递参数,从而实现更灵活的UI渲染。

  • 参数可以是状态数据,但建议使用对象的方式进行传递(直接传递,无法实现视图更新)。

  • 可以使用 Component 来抽象组件,而 @Builder 则可以实现轻量级的UI复用。

2.5 @BuilderParam

@BuilderParam用于装饰自定义组件(struct)中的属性,其装饰的属性可作为一个UI结构的占位符,待创建该组件时,可通过参数为其传入具体的内容。(其作用类似于Vue框架中的slot)。

注意⚠️:@BuilderParam装饰的方法只能被自定义构建函数(@Builder装饰的方法)初始化。

应用示例如下:


// ● 标题文字通过属性传入
// ● 内容结构“尾随闭包”传入
@Entry
@Component
struct Index {

  build() {
    Column({ space: 10 }) {
      ListModel({title:"音乐"}){
        Text("音乐的内容")
      }
      ListModel({title:"美术"}){
        Text("美术的内容")
      }
      ListModel({title:"舞蹈"})
    }.width('100%').height('100%')
  }
}

@Component
struct ListModel {
  title: string
  // ListContent 名字随便起,默认接收一个自定义构建函数
  @BuilderParam ListContent: () => void = this.defaultContent; 

  @Builder defaultContent () {
    Text('默认展示的内容')
  }

  build() {
    Column() {
      Text(this.title)
        .width('100%')
        .height(50)
        .textAlign(TextAlign.Center)
      this.ListContent();
    }.width('100%').height(200).backgroundColor(Color.Brown)
  }
}

总结:

  • 当子组件使用一个 @BuilderParam 时,使用组件时可以在尾随的大括号 {} 中插入UI结构。
  • 当子组件使用多个 @BuilderParam 时,可以在使用组件时传入参数,例如 Comp({ xxx: this.builderFn })
  • 不能既传递默认插槽也传递具名插槽,会报错。
  • 子组件本身可以提供一个默认的 @Builder 函数,作为 @BuilderParam 的备用函数,用于作为备用内容。

三、应用示例

下面应用List组件实现图文文章列表布局以及自定义模型类的使用。

ArkTS 中定义模型类与在纯 TypeScript 项目中定义类没有本质区别。但是,在 UI 开发中,你可能不会直接在组件模板中使用模型类的实例。相反,你可能会将这些实例绑定到组件的响应式状态变量上,并在模板中使用这些状态变量。这样做的好处是,当状态变量发生变化时,UI 可以自动更新。

示例代码如下:

// 定义模型类
class Item {
  id: number;
  title: string;
  img: ResourceStr;
  author: string;
  date: string;

  constructor(id: number, title: string, img: ResourceStr, author: string, date: string) {
    this.id = id
    this.title = title
    this.img = img
    this.author = author
    this.date = date
  }
}

// 全局自定义构建函数
@Builder function newsItem(item:Item){
  Row() {
    Column() {
      Text(item.title)
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
        .maxLines(2)
        .textOverflow({ overflow: TextOverflow.Ellipsis })
      Text(`${item.author} ${item.date}`)
        .fontSize(14)
    }
    .layoutWeight(1)
    .alignItems(HorizontalAlign.Start)
    .height(68)
    .justifyContent(FlexAlign.SpaceBetween)

    Image(item.img)
      .width(100)
      .height(68)
      .margin({ left: 8 })
      .borderRadius(2)
      .objectFit(ImageFit.Cover)
  }
  .width('100%')
  .height(80)
}

@Entry
@Component
struct NewsBuilder {
  @State articleList: Item[] = [
    new Item(1, "嫦娥六号探测器进入环月轨道飞行", "https://p5.img.cctvpic.com/photoworkspace/contentimg/2024/05/08/2024050820144382684.jpg", "央视网", "2014-05-08"),
    new Item(2, "“五一”假期游处处火爆 有哪些文旅新潮流?", "https://cms-emer-res.cctvnews.cctv.com/image/1005/upload/17292d188bc64cef882f1cd07d3d2acc.jpeg", "央视新闻客户端", "2024-05-08"),
    new Item(3, "杭州:全面取消住房限购,购房即可申请落户", $r("app.media.2"), "界面新闻", "2024-05-09"),
    new Item(4, "时隔五年,合肥或将再次引进“国宝”大熊猫", "https://pics4.baidu.com/feed/d50735fae6cd7b899b1d7bef7ec7b8aad8330e5d.jpeg@f_auto?token=e70fa976dceb8e88433c493f7e86b88c", "九派新闻", "2024-05-08"),
    new Item(5, "发布擦边广告再被罚,椰树集团徘徊在“土味”和“低俗”之间?", "https://pics1.baidu.com/feed/e824b899a9014c083cd8d85d067bf9057af4f471.jpeg@f_auto?token=c6770b5d0d99a68721fc88e7a337d01c", "北京商报", "2024-05-08"),
    new Item(6, "特斯拉在华推进全自动驾驶 智能网联车产业链迎新机遇", $r("app.media.2"), "每日经济新闻", "2024-05-09"),
  ]

  build() {
    Column() {
      List() {
        ForEach(this.articleList, (item: Item) => {
          ListItem() {
          // 按值传递参数
            newsItem(item)
          }
          .padding({ top: 5, bottom: 5 })
        })
      }
      .divider({ strokeWidth: 1, color: "#eeeeee" })
      .backgroundColor(0xffffff)
      .borderRadius(10)
      .padding(10)
    }.padding(10)
    .backgroundColor(0xeeeeee)
    .width('100%')
    .height('100%')
  }
}

效果图如下:
在这里插入图片描述

四、拓展阅读


网站公告

今日签到

点亮在社区的每一天
去签到