HarmonyOS鸿蒙Next中怎样实现组件复用,减少代码重复?

HarmonyOS鸿蒙Next中怎样实现组件复用,减少代码重复? 页面中有很多重复的UI结构(导航栏、列表项、卡片等),如何实现组件复用,减少代码重复?

6 回复

1、将重复组件通过@component struct进行自定义组件封装;

@Component // 装饰器
export struct C { // struct声明的数据结构
  build() { // build定义的UI
  }
}

//使用示例
import C from '...'
@Entry
@Component
struct Index{
  build(){
    Column(){
      C()
    }
  }
}

2、公共样式使用@Styles进行封装;

  • 如果每个组件的样式都需要单独设置,在开发过程中会出现大量代码在进行重复样式设置,虽然可以复制粘贴,但为了代码简洁性和后续方便维护,我们推出了可以提炼公共样式进行复用的装饰器@Styles
组件内[@Styles](/user/Styles)和全局[@Styles](/user/Styles)的用法
// 定义在全局的[@Styles](/user/Styles)封装的样式
[@Styles](/user/Styles)
function globalFancy () {
  .width(150)
  .height(100)
  .backgroundColor(Color.Pink)
}

@Entry
@Component
struct FancyUse {
  @State heightValue: number = 100;

  build() {
    Column({ space: 10 }) {
      // 使用全局的[@Styles](/user/Styles)封装的样式
      Text('FancyA')
        .globalFancy()
        .fontSize(30)
    }
  }
}

3、使用@Builder进行全局封装

  • ArkUI提供轻量的UI元素复用机制@Builder,其内部UI结构固定,仅与使用方进行数据传递。开发者可将重复使用的UI元素抽象成函数,在build函数中调用。
[@Builder](/user/Builder)
function showTextBuilder() {
  Text('Hello World')
    .fontSize(30)
    .fontWeight(FontWeight.Bold)
}
@Entry
@Component
struct BuilderDemo {
  build() {
    Column() {
      showTextBuilder()
    }
  }
}

相关文档:

更多关于HarmonyOS鸿蒙Next中怎样实现组件复用,减少代码重复?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


学习了,

基础复用方案:自定义组件封装

通过@Component装饰器将重复UI封装为独立组件,结合状态管理实现灵活复用:

[@Component](/user/Component)
struct CardItem {
  @Prop title: string  // 外部传入的标题
  @Link isSelected: boolean  // 双向绑定的选中状态

  build() {
    Column() {
      Text(this.title)
        .fontSize(18)
      Toggle({ type: ToggleType.Checkbox })
        .isOn(this.isSelected)
    }
  }
}

// 调用示例
@State selected: boolean = false

build() {
  CardItem({ title: '商品卡片', isSelected: $selected })
}

@Reusable装饰器 针对列表滚动等高频创建/销毁场景,使用组件缓存机制:

[@Reusable](/user/Reusable)  // 开启复用能力
[@Component](/user/Component)
struct ListItem {
  @Prop itemData: string

  aboutToReuse(params: Record<string, Object>) {
    this.itemData = params.itemData as string  // 复用时的数据更新
  }

  build() {
    Text(this.itemData)
      .padding(10)
  }
}

// LazyForEach中的使用
LazyForEach(dataSource, item => {
  ListItem({ itemData: item })
    .reuseId(item.type)  // 根据类型分组复用
})

@Builder构建器 对复杂UI结构进行模块化封装,支持参数传递和逻辑复用:

[@Builder](/user/Builder) function ActionButton(text: string, color: Color) {
  Button(text)
    .width(120)
    .backgroundColor(color)
    .onClick(() => {
      // 公共点击逻辑
    })
}

// 调用示例
Column() {
  ActionButton('确认', Color.Green)
  ActionButton('取消', Color.Red)
}

@Styles统一基础样式

[@Styles](/user/Styles) function commonShadow() {
  .shadow({ radius: 8, color: Color.Gray })
  .margin(10)
}

// 应用样式
Text('内容').commonShadow()

@Extend类型化样式扩展

[@Extend](/user/Extend)(Text) function warningText() {
  .fontColor('#FF4500')
  .fontWeight(FontWeight.Bold)
}

// 应用扩展
Text('警告').warningText()
  1. 简单元素优先用@Styles/@Extend
  2. 独立功能模块使用@Component封装
  3. 高频操作组件采用@Reusable+缓存策略
  4. 复杂交互结构通过@Builder解耦
  5. 列表场景必用LazyForEach+组件复用组合方案

解决方案代码实现

/**
 * 全局Builder函数
 */
[@Builder](/user/Builder)
function buildHeader(title: string, showBack: boolean = true, onBack?: () => void) {
  Row() {
    if (showBack) {
      Image($r('app.media.ic_back'))
        .width(24)
        .height(24)
        .onClick(() => {
          if (onBack) {
            onBack();
          } else {
            router.back();
          }
        })
    }
    
    Text(title)
      .fontSize(20)
      .fontWeight(FontWeight.Bold)
      .layoutWeight(1)
      .textAlign(TextAlign.Center)
    
    // 占位,保持标题居中
    if (showBack) {
      Row().width(24).height(24)
    }
  }
  .width('100%')
  .height(56)
  .padding({ left: 16, right: 16 })
  .backgroundColor(Color.White)
}

/**
 * 组件内Builder函数
 */
@Entry
@Component
struct BuilderDemo {
  @State records: Array<{name: string, amount: number, type: string}> = [
    { name: '张三', amount: 500, type: 'income' },
    { name: '李四', amount: 300, type: 'expense' }
  ];
  
  build() {
    Column() {
      // 使用全局Builder
      buildHeader('记录列表', true)
      
      List({ space: 12 }) {
        ForEach(this.records, (record: any) => {
          ListItem() {
            // 使用组件内Builder
            this.buildRecordItem(record)
          }
        })
      }
      .layoutWeight(1)
      .padding(16)
    }
  }
  
  /**
   * 组件内Builder:记录列表项
   */
  [@Builder](/user/Builder)
  buildRecordItem(record: {name: string, amount: number, type: string}) {
    Row() {
      Column({ space: 4 }) {
        Text(record.name)
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
        Text(record.type === 'income' ? '收入' : '支出')
          .fontSize(14)
          .fontColor('#999')
      }
      .alignItems(HorizontalAlign.Start)
      
      Blank()
      
      Text(`¥${record.amount}`)
        .fontSize(18)
        .fontColor(record.type === 'income' ? '#f56c6c' : '#67c23a')
    }
    .width('100%')
    .padding(16)
    .backgroundColor(Color.White)
    .borderRadius(8)
  }
  
  /**
   * 组件内Builder:空状态
   */
  [@Builder](/user/Builder)
  buildEmptyState(message: string = '暂无数据') {
    Column({ space: 12 }) {
      Image($r('app.media.ic_empty'))
        .width(120)
        .height(120)
        .opacity(0.3)
      
      Text(message)
        .fontSize(14)
        .fontColor('#999')
    }
    .width('100%')
    .padding(40)
  }
  
  /**
   * 组件内Builder:统计卡片
   */
  [@Builder](/user/Builder)
  buildStatCard(title: string, value: string, color: string) {
    Column({ space: 8 }) {
      Text(value)
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .fontColor(color)
      Text(title)
        .fontSize(14)
        .fontColor('#999')
    }
    .width('100%')
    .padding(20)
    .backgroundColor(Color.White)
    .borderRadius(12)
  }
}

/**
 * BuilderParam传递Builder
 */
@Component
struct CustomCard {
  [@BuilderParam](/user/BuilderParam) content: () => void;
  title: string = '';
  
  build() {
    Column({ space: 12 }) {
      // 标题
      Text(this.title)
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .width('100%')
      
      // 自定义内容
      this.content()
    }
    .width('100%')
    .padding(16)
    .backgroundColor(Color.White)
    .borderRadius(12)
  }
}

/**
 * 使用BuilderParam
 */
@Entry
@Component
struct BuilderParamDemo {
  build() {
    Column({ space: 12 }) {
      CustomCard({ title: '统计信息' }) {
        Column({ space: 8 }) {
          Text('总收入: ¥5000')
          Text('总支出: ¥3000')
        }
      }
      
      CustomCard({ title: '最近记录' }) {
        List({ space: 8 }) {
          ListItem() {
            Text('记录1')
          }
          ListItem() {
            Text('记录2')
          }
        }
        .height(100)
      }
    }
    .padding(16)
  }
}

/**
 * [@Extend](/user/Extend)扩展组件样式
 */
[@Extend](/user/Extend)(Text)
function primaryButton() {
  .fontSize(16)
  .fontColor(Color.White)
  .backgroundColor('#ff6b6b')
  .padding({ left: 24, right: 24, top: 12, bottom: 12 })
  .borderRadius(8)
}

[@Extend](/user/Extend)(Text)
function secondaryButton() {
  .fontSize(16)
  .fontColor('#333')
  .backgroundColor('#f5f5f5')
  .padding({ left: 24, right: 24, top: 12, bottom: 12 })
  .borderRadius(8)
}

@Component
struct ExtendDemo {
  build() {
    Column({ space: 12 }) {
      Text('确定').primaryButton()
      Text('取消').secondaryButton()
    }
  }
}

原理解析

1. @Builder全局函数

[@Builder](/user/Builder)
function buildHeader(title: string) {
  // UI代码
}
  • 定义在组件外,可被多个组件使用
  • 支持参数传递
  • 不能访问组件状态

2. @Builder组件内方法

[@Builder](/user/Builder)
buildRecordItem(record: any) {
  // UI代码
}
  • 定义在组件内,可访问组件状态
  • 使用this.buildRecordItem()调用
  • 支持参数传递

3. @BuilderParam

[@BuilderParam](/user/BuilderParam) content: () => void;
  • 接收Builder作为参数
  • 实现插槽功能
  • 提高组件灵活性

4. @Extend样式扩展

[@Extend](/user/Extend)(Text) function primaryButton() {}
  • 扩展组件样式
  • 避免重复样式代码
  • 只能扩展系统组件

最佳实践

  1. 全局Builder: 通用UI(导航栏、空状态)用全局Builder
  2. 组件Builder: 页面特定UI用组件内Builder
  3. BuilderParam: 需要自定义内容的组件用BuilderParam
  4. Extend: 重复样式用@Extend
  5. 命名规范: Builder函数以build开头

避坑指南

  1. this访问: 全局Builder不能访问this
  2. 参数类型: Builder参数要明确类型
  3. 状态更新: Builder内修改状态会触发重绘
  4. 性能: 避免在Builder内执行复杂计算
  5. Extend限制: @Extend只能用于系统组件

在HarmonyOS鸿蒙Next中,可通过ArkUI的@Builder@Extend装饰器及自定义组件实现组件复用。@Builder用于构建可复用的UI片段,@Extend可扩展组件样式。将重复UI结构封装为自定义组件,通过属性参数进行配置。同时,利用状态管理机制结合复用组件,确保数据与UI同步。

在HarmonyOS Next中,实现组件复用、减少代码重复的核心方法是使用 @Component装饰的自定义组件。这是ArkUI框架的基础能力,可以将重复的UI结构和逻辑封装成独立的、可复用的单元。

核心实现步骤:

  1. 创建自定义组件: 使用[@Component](/user/Component)装饰器定义一个struct。将需要复用的UI结构(如导航栏、列表项、卡片)的代码封装在这个struct的build()方法中。

    // 以封装一个复用的卡片组件为例
    [@Component](/user/Component)
    struct ReusableCard {
      // 1. 定义组件可接收的参数,使用@Prop或@Link装饰器实现数据同步
      @Prop cardTitle: string; // 标题,由父组件传入
      @Prop content: string;   // 内容,由父组件传入
    
      // 2. 在build方法中描述UI
      build() {
        Column() {
          Text(this.cardTitle)
            .fontSize(20)
            .fontWeight(FontWeight.Bold)
          Divider()
          Text(this.content)
            .fontSize(14)
            .margin({ top: 10 })
        }
        .padding(20)
        .backgroundColor(Color.White)
        .borderRadius(16)
        .shadow(ShadowStyle.OUTER_DEFAULT_MD)
      }
    }
    
  2. 在父组件中使用: 像使用系统内置组件(TextButton)一样,直接使用你定义的自定义组件标签,并通过属性传递参数。

    @Entry
    [@Component](/user/Component)
    struct ParentPage {
      build() {
        Column({ space: 20 }) {
          // 复用卡片1
          ReusableCard({ cardTitle: '标题一', content: '这是第一个卡片的内容区域。' })
          // 复用卡片2,传入不同的数据
          ReusableCard({ cardTitle: '标题二', content: '这是第二个卡片,拥有不同的内容。' })
          // 可以继续复用更多...
        }
        .padding(20)
        .width('100%')
        .height('100%')
        .backgroundColor(Color.F2F3F5)
      }
    }
    

关键特性与优势:

  • 数据驱动:通过@Prop@Link@State等装饰器管理组件内部状态和接收父组件参数,使UI能随数据自动更新。
  • 布局与样式封装:组件内包含完整的布局和样式,保证视觉一致性。
  • 逻辑可复用:不仅限于UI,组件的交互逻辑(如点击事件处理)也可以一并封装在内。
  • 易于维护:当需要修改该复用的UI时,只需修改自定义组件一处,所有使用该组件的地方都会同步更新。

进阶复用场景:

  • @Builder:用于构建更轻量的UI片段复用,适用于组件内或局部复用,比自定义组件更轻量。
  • @BuilderParam:允许向自定义组件传递一个[@Builder](/user/Builder)方法作为参数,实现UI结构的动态注入,极大提升了组件的灵活性。
  • @Styles@Extend:用于复用样式集合,但注意[@Extend](/user/Extend)在HarmonyOS Next中能力有所调整,建议优先使用[@Styles](/user/Styles)定义通用样式。

总结:将重复的UI结构(导航栏、列表项、卡片)提取为独立的[@Component](/user/Component)自定义组件,是HarmonyOS Next中解决代码重复、提升开发效率的标准且最有效的方式。通过属性传递数据,可以轻松实现同一组件在不同场景下的差异化呈现。

回到顶部