鸿蒙开发:熟知@BuilderParam装饰器

程序员一鸣
• 阅读 53

前言

本文代码案例基于Api13。

在实际的开发中,我们经常会遇到自定义组件的情况,比如通用的列表组件,选项卡组件等等,由于使用方的样式不一,子组件是动态变化的,针对这一情况,就不得不让使用方把子组件视图传递过来,如何来接收这个UI视图,这就是@BuilderParam装饰器的作用。

简单案例

简单封装一个通用的List组件,由于每个列表的数据和UI布局都是不一样的,那么这两块就需要暴露给使用方,代码如下:

/**
 * AUTHOR:AbnerMing
 * DATE:2025/2/13
 * INTRODUCE:通用的列表组件
 * */
@Component
export struct ListView {
  dataList?: Object[] //数据源
  @BuilderParam itemLayout: (item: Object, index: number) => void //子视图

  build() {
    List() {
      ForEach(this.dataList, (item: Object, index: number) => {
        ListItem() {
          this.itemLayout(item, index)
        }
      })
    }
  }
}

以上的自定义List组件,我们就可以用在任何页面,只需要传递数据和子视图即可。

@Entry
@Component
struct Index {

  @Builder
  itemLayout(item: Object, index: number) {
    Text(item.toString())
  }

  build() {
    Column() {
      ListView({
        dataList: [0, 1, 2, 3, 4, 5, 6],
        itemLayout: this.itemLayout
      })
    }
    .height('100%')
    .width('100%')
  }
}

@BuilderParam装饰器让UI组件赋予了动态的变化,可以根据自身需要实现不同的效果,避免了实例增加了相同的功能,从而实现更高程度的组件复用和代码解耦。

使用方式

@BuilderParam装饰器,常见于自定义组件,暴露给使用方进行调用,用来承接@Builder装饰器修饰的函数,使用方式很简单,格式如下:

@BuilderParam test: () => void

如果接收参数,直接在括号中添加即可。

除了正常的由调用者传递UI组件之外,我们也可以初始化一个默认的视图,直接在后面等于即可,这样在未传递的话就会加载默认的视图。

@Builder
testView() {

}

@BuilderParam test: () => void = this.testView()

this指向问题

如下所示,我们自定义了一个组件,定义了两个@BuilderParam,用于接收传递的UI视图:

@Component
export struct TestView {
  testContent: string = "TestView"
  @BuilderParam layout: () => void
  @BuilderParam layout2: () => void

  build() {
    Column() {
      if (this.layout != undefined) {
        this.layout()
      }
      if (this.layout2 != undefined) {
        this.layout2()
      }
    }

  }
}

我们通过三种方式,分别调用@Builder修饰的函数。

@Entry
@Component
struct Index {
  testContent: string = "Index"

  @Builder
  testView() {
    Text(`${this.testContent}`)
      .fontSize(20)
      .margin({ top: 20 })
      .fontWeight(FontWeight.Bold)
  }

  build() {
    Column() {
      this.testView()
      TestView({ layout: this.testView })
      TestView({
        layout2: () => {
          this.testView()
        }
      })
    }
    .height('100%')
    .width('100%')
  }
}

运行之后,效果如下:

鸿蒙开发:熟知@BuilderParam装饰器

我们可以看到,直接调用@Builder修饰的函数,也就是this.testView()这行代码,this指向的是当前的Index类,其值也是取的Index中值。

当我们以参数的形式,传递给@BuilderParam时,也就是TestView({ layout: this.testView })这行代码,可以发现,其this并不是指的是Index类,而是自定义组件TestView,取的是TestView中所定义的值。

当我们针对自定义组件,换种方式使用时,也就是如下方式使用:

TestView({
        layout2: () => {
          this.testView()
        }
      })

可以发现,this又切换为了当前类Index,这是因为箭头函数的this指向的是宿主对象,所以其值取的是Index类中的。

所以在有@BuilderParam传递UI视图时,一定要注意this的指向问题,这也是为什么很多同学遇到在@Builder修饰的函数中为什么不刷新数据的问题,其原因就是this指向不对。

数据更新

更新@BuilderParam装饰器,本意就是更新对应的@Builder修饰的函数,这个在《鸿蒙开发:了解@Builder装饰器》一篇中已经重点做了讲解,这里再重新概述一下。

简单自定义一个组件,使用 @BuilderParam装饰器,对外暴露一个UI视图。

@Component
export struct TestView {
  @BuilderParam layout: () => void

  build() {
    Column() {
      if (this.layout != undefined) {
        this.layout()
      }
    }
  }
}

上面已经讲述过this指向问题了,如果数据在本页面内,那么一定要使用箭头函数来调用@Builder修饰的函数,才能实现数据的更新。

@Entry
@Component
struct Index {
  @State testContent: string = "测试数据一"

  @Builder
  textView() {
    Text(this.testContent)
      .fontSize(20)
      .fontWeight(FontWeight.Bold)
  }

  build() {
    Column() {
      TestView({
        layout: () => {
          this.textView()
        }
      })
      Button("点击")
        .onClick(() => {
          this.testContent = "测试数据二"
        })
    }
    .height('100%')
    .width('100%')
  }
}

相关总结

在声明@BuilderParam的时候,如果未有默认值,那么在不传递的情况下,会发生异常崩溃,为了解决这一问题,有两种方案,方案一,主动设置默认值:

@Builder
testView() {

}

@BuilderParam test: () => void = this.testView()

方案二,在调用的地方进行非空校验:

@BuilderParam test: () => void

  build() {
    if (this.test != undefined) {
      this.test()
    }
  }

@BuilderParam用于接收@Builder定义的函数,私有和全局都可以。

定义全局的@Builder。

@Builder
export function TextView() {
  Text("测试数据一")
    .fontSize(20)
    .fontWeight(FontWeight.Bold)
}

调用

@Entry
@Component
struct Index {
  build() {
    Column() {
      TestView({ layout: TextView })
    }
    .height('100%')
    .width('100%')
  }
}
点赞
收藏
评论区
推荐文章
Stella981 Stella981
3年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Easter79 Easter79
3年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Stella981 Stella981
3年前
JS 中的this指向问题和call、apply、bind的区别
this的指向问题一般情况下this对象指向调用函数的对象,全局环境中执行函数this对象指向window。functiona(){console.log(this);//输出函数a中的this对象}functionb(){};varc{name:"call"}
Wesley13 Wesley13
3年前
JavaWeb之动态代理解决request请求编码问题
动态代理解决编码问题1.设计模式出现原因:软件开发过程中,遇到相似问题,将问题的解决方法抽取模型(套路)常见设计模式:单例,工厂,适配器,装饰者,动态代理。2.装饰者模式简单介绍谷歌汽车开发场景1.Java定义了汽车开发约定interfaceICar{s
Easter79 Easter79
3年前
SwiftUI 跨组件数据传递
作者:Cyandev,iOS和MacOS开发者,目前就职于字节跳动0x00前言众所周知,SwiftUI的开发模式与React、Flutter非常相似,即都是声明式UI,由数据驱动(产生)视图,视图也会与数据自动保持同步,框架层会帮你处理“绑定”的问题。在声明式UI中不存在命令式地让一个视图变成xxx
程序员一鸣 程序员一鸣
2天前
鸿蒙开发:了解@Builder装饰器
@Builder装饰是鸿蒙UI开发中,非常重要的一个装饰器,在实际的开发中,合理且正确的使用,能够让我们的代码更加的简洁,有两点需要注意,一是,是用私有还是全局,取决于当前的组件的复用机制,如果多个页面都使用了,建议以全局为主;二是传参的动态更新,有更新就使用引用参数传递,没有更新按值传递即可。
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究
京东云开发者 京东云开发者
9个月前
鸿蒙跨端实践-ArkTS和CAPI的混合开发实现
一、背景在文章中,讲述了动态化适配鸿蒙的方案实现,当在鸿蒙系统进行UI渲染的时候,我们使用了系统的组件进行递归渲染。在iOS和Android也是借助各自系统组件进行的渲染,但是在鸿蒙系统会存在以下4个严重问题:1.UI层级过多以金融APP理财频道页中的一个
美凌格栋栋酱 美凌格栋栋酱
4个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
飞龙AI 飞龙AI
2星期前
DevEcoStudio 中使用模拟器时如何过滤日志
DevEcoStudio中使用模拟器时如何过滤日志鸿蒙核心技术鸿蒙开发者工具DevEcoStudio在HilogSettingsFilter设置Logmessage:A03d00/JSAPP当你看到不断更新的日志时,你会不会崩溃因为Nofilters模式下