当前位置: 首页 > news >正文

【HarmonyOS实战】 @Builder构建函数:UI复用的正确姿势

文章目录

    • 前言
    • 一、@Builder 是什么?
    • 二、@Builder vs @Component
    • 三、项目中的三个 @Builder
      • 3.1 titleBuilder:标题栏
      • 3.2 bindBuilder:底部弹窗内容
      • 3.3 stationInfoCard:加油站卡片(带参数)
    • 四、全局 @Builder
    • 五、@Builder 的限制
      • 5.1 不能有独立的 @State
      • 5.2 参数传递限制
    • 六、什么时候用 @Builder,什么时候用 @Component?
    • 七、实际建议:如何拆分复杂页面
    • 总结

前言

打开GasStationPage.ets,你会发现有三个@Builder装饰的方法:stationInfoCardbindBuildertitleBuilder。这些方法都返回 UI,但又不是独立的@Component组件。

@Builder到底是什么?和组件有什么区别?什么时候用@Builder,什么时候用@Component?这篇文章讲清楚这些问题。

项目预览

一、@Builder 是什么?

@Builder是一个轻量级的 UI 复用机制,可以把一段 UI 代码抽取成一个"可调用的 UI 片段"。

@Entry@Componentstruct MyPage{// 定义 @Builder:把一段 UI 抽取出来@BuilderheaderBuilder(){Row(){Text('标题').fontSize(20).fontWeight(700)}.width('100%').height(50)}build(){Column(){this.headerBuilder()// 调用 @Builder,就像调用函数一样// 其他内容...}}}

二、@Builder vs @Component

特性@Builder@Component
定义方式函数(方法)结构体(struct)
状态管理共享父组件的状态独立的状态作用域
参数传递直接函数参数通过 @Prop/@Link 等
生命周期无独立生命周期有完整生命周期
复用粒度UI 片段,轻量独立组件,重量
适用场景复杂页面内部拆分跨页面复用的独立组件

核心区别@Builder里的 UI 是父组件的一部分,共享父组件的this(即可以直接访问父组件的状态变量);@Component是独立的组件,有自己的状态作用域。

三、项目中的三个 @Builder

3.1 titleBuilder:标题栏

@BuildertitleBuilder(){Row({space:Constants.SPACE_8}){Image($r('app.media.back')).width(Constants.IMAGE_BACK_WIDTH)// 40.height(Constants.IMAGE_BACK_HEIGHT)// 40.onClick(()=>{this.pageInfos.pop();// 直接访问父组件的 pageInfos!});Text($r('app.string.car_life')).fontWeight(Constants.FONT_WEIGHT_700).fontSize(Constants.FONT_SIZE_20).lineHeight(Constants.LINE_HEIGHT_27).fontColor($r('app.color.gas_station_name_color'));}.width(Constants.FULL_PERCENT).padding({left:Constants.PADDING_RIGHT_16}).position({top:Constants.POSITION_TOP});}

注意this.pageInfos.pop()——@Builder里直接访问父组件(GasStationPage)的pageInfos,这是@Component做不到的(子组件需要通过@Prop/@Link接收数据)。

为什么用 @Builder 而不是 @Component?
因为标题栏只在这一个页面用,而且需要直接访问页面的路由栈(pageInfos),用@Builder是最简单的方式。

3.2 bindBuilder:底部弹窗内容

@BuilderbindBuilder(){Column(){Scroll(){if(this.stationInfoList&&this.stationInfoList.length>0){List(){ForEach(this.stationInfoList,(station:StationData)=>{// 直接用 stationInfoListListItem(){Row({space:Constants.SPACE_12}){this.stationInfoCard(station);// 调用另一个 @Builder}.width(Constants.FULL_PERCENT);};},(station:StationData)=>{returnstation.id+station.name;});}.width(Constants.FULL_PERCENT);}}// ...样式}.height(Constants.MY_BUILDER_COLUMN_HEIGHT);}

@Builder里可以调用另一个 @Builderthis.stationInfoCard(station)就是在bindBuilder里调用了stationInfoCard

3.3 stationInfoCard:加油站卡片(带参数)

@BuilderstationInfoCard(gasStation:StationData):void{// 注意:有参数!Column({space:Constants.SPACE_12}){// 卡片内容....onClick(()=>{if(gasStation){this.openOrCloseMap(true);// 访问父组件方法this.moveToGasStation(gasStation.latitude,gasStation.longitude);// 访问父组件方法}else{this.getUIContext().getPromptAction().showToast({message:$r('app.string.Stay_tuned')});}});}}

@Builder可以有参数stationInfoCard接收一个StationData参数,每次调用传入不同的数据,渲染不同的卡片。

调用时:

this.stationInfoCard(station)// 传入当前循环的加油站数据

四、全局 @Builder

上面三个都是组件内部的局部 @Builder(作为组件的方法)。@Builder也可以定义在组件外部,成为全局 @Builder

// 文件顶层定义(全局)@BuilderexportfunctionGasStationPageBuilder(){GasStationPage();}// 可以在任何地方调用GasStationPageBuilder();

为什么GasStationPageBuilder是全局 @Builder?

因为它是 Navigation 路由系统的入口。route_map.json里配置了buildFunction: "GasStationPageBuilder",框架需要通过这个全局函数来创建页面,所以必须是全局可访问的(export function)。

全局@Builder和组件方法的区别:

  • 全局:不属于任何组件,没有this,不能访问组件状态
  • 局部(组件方法):属于组件,有this,可以访问组件的属性和方法

五、@Builder 的限制

5.1 不能有独立的 @State

@BuildermyBuilder(){@Statecount:number=0;// ❌ 报错!@Builder 里不能定义 @StateText(`${count}`)}

@Builder不能定义自己的状态变量。如果需要状态,要么:

  • 用父组件的状态变量(共享状态)
  • 把 UI 抽成真正的@Component

5.2 参数传递限制

// 简单类型参数:按值传递,父组件状态变化不会同步到 Builder 里@BuildermyBuilder(text:string){Text(text)// text 变化不会自动刷新(除非父组件重新调用)}// 对象类型参数:传引用可以观察到变化@BuildermyBuilder(config:{text:string}){Text(config.text)// config 对象里的属性变化可以被观察到}

提示:如果@Builder里的 UI 需要响应父组件某个值的变化,建议直接用this.xxx访问父组件的@State变量,而不是通过参数传递。

六、什么时候用 @Builder,什么时候用 @Component?

用 @Builder 当

  • UI 片段只在当前组件内使用
  • 需要直接访问父组件的状态变量
  • UI 片段不需要独立的状态和生命周期
  • 只是为了让代码更整洁,拆分大build()函数

用 @Component 当

  • UI 组件需要在多个页面复用
  • 组件需要独立的状态管理
  • 组件有复杂的生命周期(aboutToAppearonWillDisappear等)
  • 组件需要接受外部的数据绑定(@Prop@Link@ObjectLink

七、实际建议:如何拆分复杂页面

面对一个复杂页面,推荐这样的拆分策略:

GasStationPage(@Component @Entry) ├── titleBuilder(@Builder) ← 只此页面用,需要访问页面路由 ├── bindBuilder(@Builder) ← 只此页面用,需要访问页面数据 │ └── stationInfoCard(@Builder) ← 复杂卡片,提取为 Builder

如果stationInfoCard未来需要在其他地方复用,就把它抽成独立的@Component

// StationCard.ets@Componentstruct StationCard{@PropgasStation:StationData;onClickCallback:(station:StationData)=>void=()=>{};build(){// 卡片 UI}}

总结

@Builder是 ArkUI 中轻量的 UI 复用机制:

  1. 局部 @Builder(组件方法):共享父组件状态,可有参数,适合拆分同一页面的 UI 片段
  2. 全局 @Builder(文件顶层函数):无this,适合路由入口等全局调用场景
  3. vs @Component:@Builder 轻量但功能有限,@Component 独立但较重

GasStationPage用三个 @Builder 把复杂的build()方法拆分成几个有意义的部分,代码可读性大大提升。

下一篇讲LocationKit 定位服务——geoLocationManager是怎么获取用户位置的,异步获取位置有哪些注意事项。

http://www.jsqmd.com/news/965764/

相关文章:

  • 别再问FPGA是啥了!用面包板和“黑方块”的故事,带你5分钟搞懂它的前世今生
  • 效率革命:跳过下载安装与配置,用快马AI即刻生成Vue3项目框架
  • 国产硬件仿真工具在AI芯片和HPC大芯片验证中的应用现状
  • 提升i2c调试效率:用快马平台一键生成总线扫描与诊断工具代码
  • 别再死记硬背公式了!用Python模拟带你直观理解马尔可夫链的收敛过程
  • APDS9930手势传感器避坑指南:在Arduino Uno上实现稳定手势识别的5个关键点
  • SAP FIBF实战:手把手教你用BTE增强搞定会计凭证字段自动替换
  • 告别硬件SPI资源紧张:用GPIO模拟驱动ADS8684/8688的避坑指南与性能实测
  • Java SpringBoot+Vue3+MyBatis 开发精简博客系统系统源码|前后端分离+MySQL数据库
  • Sobolev-Lorentz嵌入在Cartan-Hadamard流形上的最优性研究
  • 从Eclipse老手到STS新手:一份无缝迁移的避坑指南与个性化配置清单
  • 从WRF输出变量到天气分析:手把手教你用NCL提取关键气象要素(以一次暴雨过程为例)
  • 从论文拒稿到接收:LaTeX子图标签(label)和引用(ref)的避坑指南
  • 别再被‘抖振’劝退!用Python从零实现一个简单的滑模控制器(附完整代码)
  • 从F1赛车到无人机:聊聊脉冲雷达‘距离模糊’在现实中的那些事儿
  • 【HarmonyOS实战】 LocationKit定位服务:获取用户位置完整指南
  • Matlab鱼雷刚体运动仿真:俯仰/偏航/深度/航速四维动态可视化
  • 无需鼠标!借助键盘实现快速鼠标控制
  • MicroPython固件“魔改”指南:以BLACK_F407ZG为例,自定义你的板载LED、串口和SPI引脚
  • 别再只盯着GPS了!精度因子(DOP)在Wi-Fi/蓝牙定位里同样关键
  • 当“观察力”成为产品核心:从一篇小说看如何设计真正“被看见”的用户体验
  • 从数据到洞察:手把手教你用Python处理卫星测高数据计算SLA/SSHA
  • ai一键生成vivado安装验证脚本,快速搭建fpga开发环境
  • 从F1赛车到无人机避障:聊聊脉冲雷达‘测不准’的那些事儿与工程解法
  • KMS智能激活工具:高效解决Windows和Office激活难题
  • CPU上的LLM推理加速:AMX指令集与稀疏化技术
  • 给奈奎斯特图‘加点料’:一个零点如何让系统频率响应大变样?
  • 高效Windows内存优化指南:3步掌握Mem Reduct智能内存管理技巧
  • 告别环境冲突:用Docker一键部署Matconvnet(支持Matlab 2020b + CUDA 11)
  • 瑞萨e2 studio调试配置全解析:Connection Settings里那个200mA选项到底该不该勾?