找回密码
 立即注册
首页 业界区 业界 鸿蒙应用开发从入门到实战(十八):组件编程思想之代码 ...

鸿蒙应用开发从入门到实战(十八):组件编程思想之代码复用

系味 昨天 11:37
大家好,我是潘Sir,持续分享IT技术,帮你少走弯路。《鸿蒙应用开发从入门到项目实战》系列文章持续更新中,陆续更新AI+编程、企业级项目实战等原创内容、欢迎关注!
ArkUI提供了丰富的系统组件,用于制作鸿蒙原生应用APP的UI,在制作UI时会经常遇到代码或样式重复问题,本文通过ArkUI提供的适配器实现代码复用。
一、样式复用

1.1 概述

当多个组件具有相同的样式时,若每个组件都单独设置,将会有大量的重复代码。为避免重复代码,开发者可使用@Styles或者@Extend装饰器将多条样式设置提炼成一个方法,然后直接在各组件声明的位置进行调用,这样就能完成样式的复用。
1.webp

1.2 @Styles方法

@Styles方法可定义在组件内或者全局,具体语法如下

  • 组件内
  1. @Entry
  2. @Component
  3. struct StylesPage {
  4.   build() {
  5.     Column() {
  6.       Row({ space: 50 }) {
  7.         Button('确认')
  8.           .type(ButtonType.Normal)
  9.           .backgroundColor(Color.Green)
  10.           .compButtonStyle() //复用样式
  11.           .onClick(() => console.log('确认'))
  12.         Button('取消')
  13.           .type(ButtonType.Normal)
  14.           .backgroundColor(Color.Gray)
  15.           .compButtonStyle() //复用样式
  16.           .onClick(() => console.log('取消'))
  17.       }
  18.     }.width('100%')
  19.     .height('100%')
  20.     .justifyContent(FlexAlign.Center)
  21.   }
  22.   //组件内样式定义
  23.   @Styles compButtonStyle() {
  24.     .width(100)
  25.     .height(40)
  26.     .borderRadius(10)
  27.   }
  28. }
复制代码

  • 全局
  1. @Entry
  2. @Component
  3. struct StylesPage {
  4.   build() {
  5.     Column() {
  6.       Row({ space: 50 }) {
  7.         Button('确认')
  8.           .type(ButtonType.Normal)
  9.           .backgroundColor(Color.Green)
  10.           .globalButtonStyle() //复用样式
  11.           .onClick(() => console.log('确认'))
  12.         Button('取消')
  13.           .type(ButtonType.Normal)
  14.           .backgroundColor(Color.Gray)
  15.           .globalButtonStyle() //复用样式
  16.           .onClick(() => console.log('取消'))
  17.       }
  18.     }.width('100%')
  19.     .height('100%')
  20.     .justifyContent(FlexAlign.Center)
  21.   }
  22. }
  23. //全局样式定义
  24. @Styles function globalButtonStyle() {
  25.   .width(100)
  26.   .height(40)
  27.   .borderRadius(10)
  28. }
复制代码
注意

  • 组件内的@Styles方法只能在当前组件中使用,全局的@Styles方法目前只允许在当前的.ets文件中使用
  • 组件内定义@Styles方法时不需要使用function关键字,全局的@Styles方法需要使用function关键字
  • @Styles方法中只能包含通用属性方法通用事件方法
  • @Styles方法不支持参数
示例代码
pages/component目录下新建resue目录,新建StylesPage.ets文件
  1. @Entry
  2. @Component
  3. struct StylesPage {
  4.   build() {
  5.     Column() {
  6.       Row({ space: 50 }) {
  7.         Button('确认')
  8.           .type(ButtonType.Normal)
  9.           .backgroundColor(Color.Green)
  10.           .compButtonStyle() //复用样式
  11.             // .globalButtonStyle() //复用样式
  12.           .onClick(() => console.log('确认'))
  13.         Button('取消')
  14.           .type(ButtonType.Normal)
  15.           .backgroundColor(Color.Gray)
  16.           .compButtonStyle() //复用样式
  17.             // .globalButtonStyle() //复用样式
  18.           .onClick(() => console.log('取消'))
  19.       }
  20.     }.width('100%')
  21.     .height('100%')
  22.     .justifyContent(FlexAlign.Center)
  23.   }
  24.   //组件内样式定义
  25.   @Styles compButtonStyle() {
  26.     .width(100)
  27.     .height(40)
  28.     .borderRadius(10)
  29.   }
  30. }
  31. //全局样式定义
  32. @Styles function globalButtonStyle() {
  33.   .width(100)
  34.   .height(40)
  35.   .borderRadius(10)
  36. }
复制代码
1.3 @Extend方法

@Extend装饰的方法同样可用于组件样式的复用,与@Styles不同的是,@Extend方法只能定义在全局。并且@Extend方法只能用于指定类型的组件,例如以下方法只能用于Button组件(可以理解为是Button组件的扩展样式)
  1. @Extend(Button) function buttonStyle(){
  2.   ...
  3. }
复制代码
由于@Extend方法只能用于指定类型的组件,因此方法中可包含指定组件的专有属性方法专有事件方法。另外,@Extend方法还支持参数,具体语法如下
  1. @Entry
  2. @Component
  3. struct ExtendPage {
  4.   build() {
  5.     Column() {
  6.       Row({ space: 50 }) {
  7.         Button('确认')
  8.           .buttonExtendStyle(Color.Green, () => console.log('确认')) //复用样式
  9.         Button('取消')
  10.           .buttonExtendStyle(Color.Gray, () => console.log('取消')) //复用样式
  11.       }
  12.     }.width('100%')
  13.     .height('100%')
  14.     .justifyContent(FlexAlign.Center)
  15.   }
  16. }
  17. //样式定义
  18. @Extend(Button) function buttonExtendStyle(color: Color, callback: () => void) {
  19.   .width(100)
  20.   .height(40)
  21.   .borderRadius(10)
  22.   .type(ButtonType.Normal)
  23.   .backgroundColor(color)
  24.   .onClick(callback)
  25. }
复制代码
总结

  • @Extend方法只能定义在全局,使用范围目前只限于当前的.ets文件
  • @Extend方法用于特定类型的组件,因此可包含该组件的专有属性方法专有事件方法
  • @Extend方法支持参数
示例代码
pages/component/resue目录下新建ExtendPage.ets文件
  1. @Entry
  2. @Component
  3. struct ExtendPage {
  4.   build() {
  5.     Column() {
  6.       Row({ space: 50 }) {
  7.         Button('确认')
  8.           .buttonExtendStyle(Color.Green, () => console.log('确认')) //复用样式
  9.         Button('取消')
  10.           .buttonExtendStyle(Color.Gray, () => console.log('取消')) //复用样式
  11.       }
  12.     }.width('100%')
  13.     .height('100%')
  14.     .justifyContent(FlexAlign.Center)
  15.   }
  16. }
  17. //样式定义
  18. @Extend(Button) function buttonExtendStyle(color: Color, callback: () => void) {
  19.   .width(100)
  20.   .height(40)
  21.   .borderRadius(10)
  22.   .type(ButtonType.Normal)
  23.   .backgroundColor(color)
  24.   .onClick(callback)
  25. }
复制代码
二、UI结构复用

2.1 概述

当页面有多个相同的UI结构时,若每个都单独声明,同样会有大量重复的代码。为避免重复代码,可以将相同的UI结构提炼为一个自定义组件,完成UI结构的复用。
除此之外,ArkTS还提供了一种更轻量的UI结构复用机制@Builder方法,开发者可以将重复使用的UI元素抽象成一个@Builder方法,该方法可在build()方法中调用多次,以完成UI结构的复用。
2.webp

2.2 语法说明

@Builder方法同样可以定义在组件内或者全局,具体语法如下

  • 组件内
  1. @Entry
  2. @Component
  3. struct BuilderPage {
  4.   build() {
  5.     Column() {
  6.       Row({ space: 50 }) {
  7.         //复用UI结构
  8.         this.compButtonBuilder($r('app.media.icon_edit'), '编辑', () => console.log('编辑'))
  9.         this.compButtonBuilder($r('app.media.icon_send'), '发送', () => console.log('发送'))
  10.       }
  11.     }.width('100%')
  12.     .height('100%')
  13.     .justifyContent(FlexAlign.Center)
  14.   }
  15.   //定义UI结构
  16.   @Builder compButtonBuilder(icon: Resource, text: string, callback: () => void) {
  17.     Button() {
  18.       Row({ space: 10 }) {
  19.         Image(icon)
  20.           .width(25)
  21.           .height(25)
  22.         Text(text)
  23.           .fontColor(Color.White)
  24.           .fontSize(25)
  25.       }
  26.     }.width(120)
  27.     .height(50)
  28.     .onClick(callback)
  29.   }
  30. }
复制代码

  • 全局
  1. @Entry
  2. @Component
  3. struct BuilderPage {
  4.   build() {
  5.     Column() {
  6.       Row({ space: 50 }) {
  7.         //复用UI结构
  8.         globalButtonBuilder($r('app.media.icon_edit'), '编辑', () => console.log('编辑'))
  9.         globalButtonBuilder($r('app.media.icon_send'), '发送', () => console.log('发送'))
  10.       }
  11.     }.width('100%')
  12.     .height('100%')
  13.     .justifyContent(FlexAlign.Center)
  14.   }
  15. }
  16. //定义UI结构
  17. @Builder function globalButtonBuilder(icon: Resource, text: string, callback: () => void) {
  18.     Button() {
  19.       Row({ space: 10 }) {
  20.         Image(icon)
  21.           .width(25)
  22.           .height(25)
  23.         Text(text)
  24.           .fontColor(Color.White)
  25.           .fontSize(25)
  26.       }
  27.     }.width(120)
  28.     .height(50)
  29.     .onClick(callback)
  30. }
复制代码
注意

  • 组件内的@Builder方法可通过this访问当前组件的属性和方法,而全局的@Builder方法则不能
  • 组件内的@Builder方法只能用于当前组件,全局的@Builder方法导出(export)后,可用于整个应用。
示例代码
拷贝icon_edit.png和icon_send.png文件到resources/base/media目录
pages/component/resue目录下新建BuilderPage.ets文件
  1. @Entry
  2. @Component
  3. struct BuilderPage {
  4.   build() {
  5.     Column() {
  6.       Row({ space: 50 }) {
  7.         this.compButtonBuilder($r('app.media.icon_edit'), '编辑', () => console.log('编辑'))
  8.         this.compButtonBuilder($r('app.media.icon_send'), '发送', () => console.log('发送'))
  9.         // globalButtonBuilder($r('app.media.icon_edit'), '编辑', () => console.log('编辑'))
  10.         // globalButtonBuilder($r('app.media.icon_send'), '发送', () => console.log('发送'))
  11.       }
  12.     }.width('100%')
  13.     .height('100%')
  14.     .justifyContent(FlexAlign.Center)
  15.   }
  16.   @Builder compButtonBuilder(icon: Resource, text: string, callback: () => void) {
  17.     Button() {
  18.       Row({ space: 10 }) {
  19.         Image(icon)
  20.           .width(25)
  21.           .height(25)
  22.         Text(text)
  23.           .fontColor(Color.White)
  24.           .fontSize(25)
  25.       }
  26.     }.width(120)
  27.     .height(50)
  28.     .onClick(callback)
  29.   }
  30. }
  31. @Builder function globalButtonBuilder(icon: Resource, text: string, callback: () => void) {
  32.   Button() {
  33.     Row({ space: 10 }) {
  34.       Image(icon)
  35.         .width(25)
  36.         .height(25)
  37.       Text(text)
  38.         .fontColor(Color.White)
  39.         .fontSize(25)
  40.     }
  41.   }.width(120)
  42.   .height(50)
  43.   .onClick(callback)
  44. }
复制代码
2.3 @Builder方法参数传递规则

@Builder方法具有两种参数传递机制——按值传递按引用传递。当只有一个参数且参数为对象字面量时为按引用传递,其余情况均为按值传递。
按引用传递时,若传递的参数为状态变量,则状态变量的变化将会触发@Builder方法内部UI的刷新;按值传递时则不会。
示例代码
pages/component/resue目录下新建BuilderParameterPage.ets文件
  1. @Entry
  2. @Component
  3. struct BuilderParameterPage {
  4.   @State count: number = 0;
  5.   build() {
  6.     Column({ space: 50 }) {
  7.       //按值传递
  8.       valueTextBuilder(this.count)
  9.       //按引用传递
  10.       referenceTextBuilder({ count: this.count })
  11.       Row({ space: 50 }) {
  12.         Button('-1').onClick(() => {
  13.           this.count--;
  14.         })
  15.         Button('+1').onClick(() => {
  16.           this.count++;
  17.         })
  18.       }
  19.     }.width('100%')
  20.     .height('100%')
  21.     .justifyContent(FlexAlign.Center)
  22.   }
  23. }
  24. @Builder function valueTextBuilder(count: number) {
  25.   Text(`按值传递: ${count}`)
  26.     .fontSize(30)
  27.     .fontWeight(FontWeight.Bold)
  28. }
  29. @Builder function referenceTextBuilder(obj: { count: number }) {
  30.   Text(`按引用传递: ${obj.count}`)
  31.     .fontSize(30)
  32.     .fontWeight(FontWeight.Bold)
  33. }
复制代码
2.4 @Builder方法和自定义组件的区别

@Builder方法和自定义组件虽然都可以实现UI复用的效果,但是两者还是有着本质的区别的,其中最为显著的一个区别就是自定义组件可以定义自己的状态变量,而@Builder方法则不能。
以下案例中,每个待办事项的UI结构都相同,因此可考虑将其提炼为一个自定义组件或者@Builder方法,但是由于每个待办事项均有已完成未完成两种状态,因此需要为每个待办事项都定义一个状态变量,所以此时就只能使用自定义组件而不能使用@Builder方法。
3.gif

总结
若复用的UI结构没有状态,推荐使用@Builder方法,否则使用自定义组件。
示例代码
pages/component/resue目录下新建DifferencePage.ets文件
  1. @Entry
  2. @Component
  3. struct DifferencePage {
  4.   build() {
  5.     Column({ space: 10 }) {
  6.       Text('待办事项')
  7.         .fontSize(30)
  8.         .fontWeight(FontWeight.Bold)
  9.         .width('100%')
  10.       TodoItem({ text: '读书' })
  11.       TodoItem({ text: '运动' })
  12.       TodoItem({ text: '早睡' })
  13.     }
  14.     .width('100%')
  15.     .height('100%')
  16.     .justifyContent(FlexAlign.Start)
  17.     .padding(10)
  18.     .backgroundColor('#f2f2f2')
  19.   }
  20. }
  21. @Component
  22. struct TodoItem {
  23.   text: string;
  24.   @State isDone: boolean = false;
  25.   build() {
  26.     Row() {
  27.       Text(this.text)
  28.         .fontSize(30)
  29.         .fontWeight(FontWeight.Medium)
  30.           //文本装饰线,根据isDone的值选择不同的类型
  31.         .decoration({ type: this.isDone ? TextDecorationType.LineThrough : TextDecorationType.None })
  32.       //用于填充Column/Row容器的剩余空间
  33.       Blank()
  34.       Toggle({ type: ToggleType.Checkbox })
  35.         .onChange((value) => {
  36.           this.isDone = value;
  37.         })
  38.     }
  39.     .width('100%')
  40.     .height(60)
  41.     .backgroundColor(Color.White)
  42.     .padding(10)
  43.     .borderRadius(10)
  44.   }
  45. }
复制代码
2.5 @BuilderParam

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

  • 组件定义
  1. @Component
  2. struct Container {
  3.    //@BuilderParam属性
  4.   @BuilderParam content: () => void
  5.   build() {
  6.     Column() {
  7.       Text('其他内容') //其他内容
  8.       this.content(); //占位符
  9.       Button('其他内容') //其他内容
  10.     }
  11.   }
  12. }
复制代码

  • UI结构定义
  1. @Builder function contentBuilder1() {
  2.   ...
  3. }
  4. @Builder function contentBuilder2() {
  5.   ...
  6. }
  7. @Builder function contentBuilder3() {
  8.   ...
  9. }
复制代码

  • 组件创建
  1. Container({ content: contentBuilder1 })
  2. Container({ content: contentBuilder2 })
  3. Container({ content: contentBuilder3 })
复制代码
下面通过一个案例展示@BuilderParam的具体用法,例如,现需要实现一个通用的卡片组件,如下图所示
4.webp

卡片中显示的内容不固定,例如
5.webp

具体实现步骤如下:
(1)卡片组件定义
  1. @Component
  2. struct Card {
  3.   @BuilderParam content: () => void; //@BuilderParam属性
  4.   build() {
  5.     Column() {
  6.       this.content(); //占位符
  7.     }.width('90%')
  8.     .padding(10)
  9.     .borderRadius(10)
  10.     .shadow({ radius: 20 })
  11.   }
  12. }
复制代码
效果
6.webp

(2)卡片内容定义
  1. @Builder function imageBuilder() {
  2.   Column({ space: 10 }) {
  3.     Image($r('app.media.img_harmony'))
  4.       .width(300)
  5.       .height(150)
  6.     Text('鸿蒙操作系统')
  7.   }
  8. }
复制代码
效果图
7.webp

(3)创建卡片组件
  1. Card({ content: imageBuilder })
复制代码
8.webp

另外,如果一个组件中只定义了一个@BuilderParam属性,那么创建该组件时,也可直接通过"子组件"的方式传入具体的UI结构,例如
创建卡片组件
  1. Card() {
  2.   Column({ space: 10 }) {
  3.     Text('鸿蒙操作系统')
  4.       .fontSize(25)
  5.       .fontWeight(FontWeight.Bold)
  6.     Text('鸿蒙操作系统是...')
  7.   }
  8. }
复制代码
效果图
9.webp

示例代码
pages/component/resue目录下新建BuilderParamPage2.ets文件
  1. @Entry@Componentstruct BuilderParamPage2 {  build() {    Column({ space: 50 }) {      //创建卡片组件(传参)      Card({ content: imageBuilder })      //创建卡片组件("子组件")      Card() {        Column({ space: 10 }) {          Text('鸿蒙操作系统')            .fontSize(25)            .fontWeight(FontWeight.Bold)          Text('鸿蒙操作系统是一款由华为公司开发的多设备统一操作系统,致力于实现无缝连接和协同工作。其采用分布式架构,支持多终端智能互联,提供高效、安全、流畅的用户体验。')        }      }    }.width('100%')    .height('100%')    .justifyContent(FlexAlign.Center)  }}//卡片内容@Builder function imageBuilder() {
  2.   Column({ space: 10 }) {
  3.     Image($r('app.media.img_harmony'))
  4.       .width(300)
  5.       .height(150)
  6.     Text('鸿蒙操作系统')
  7.   }
  8. }//卡片组件@Component
  9. struct Card {
  10.   @BuilderParam content: () => void; //@BuilderParam属性
  11.   build() {
  12.     Column() {
  13.       this.content(); //占位符
  14.     }.width('90%')
  15.     .padding(10)
  16.     .borderRadius(10)
  17.     .shadow({ radius: 20 })
  18.   }
  19. }
复制代码
《鸿蒙应用开发从入门到项目实战》系列文章持续更新中,陆续更新AI+编程、企业级项目实战等原创内容,防止迷路,欢迎关注!

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册