HarmonyOS鸿蒙Next中怎样实现组件复用,减少代码重复?
HarmonyOS鸿蒙Next中怎样实现组件复用,减少代码重复? 页面中有很多重复的UI结构(导航栏、列表项、卡片等),如何实现组件复用,减少代码重复?
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()
- 简单元素优先用@Styles/@Extend
- 独立功能模块使用@Component封装
- 高频操作组件采用@Reusable+缓存策略
- 复杂交互结构通过@Builder解耦
- 列表场景必用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() {}
- 扩展组件样式
- 避免重复样式代码
- 只能扩展系统组件
最佳实践
- 全局Builder: 通用UI(导航栏、空状态)用全局Builder
- 组件Builder: 页面特定UI用组件内Builder
- BuilderParam: 需要自定义内容的组件用BuilderParam
- Extend: 重复样式用@Extend
- 命名规范: Builder函数以build开头
避坑指南
- this访问: 全局Builder不能访问this
- 参数类型: Builder参数要明确类型
- 状态更新: Builder内修改状态会触发重绘
- 性能: 避免在Builder内执行复杂计算
- Extend限制: @Extend只能用于系统组件
在HarmonyOS Next中,实现组件复用、减少代码重复的核心方法是使用 @Component装饰的自定义组件。这是ArkUI框架的基础能力,可以将重复的UI结构和逻辑封装成独立的、可复用的单元。
核心实现步骤:
-
创建自定义组件: 使用
[@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) } } -
在父组件中使用: 像使用系统内置组件(
Text、Button)一样,直接使用你定义的自定义组件标签,并通过属性传递参数。@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中解决代码重复、提升开发效率的标准且最有效的方式。通过属性传递数据,可以轻松实现同一组件在不同场景下的差异化呈现。

