🎊 鸿蒙 自定义组件封装(带Props/Events的通用卡片组件)

鸿蒙 自定义组件封装(带Props/Events的通用卡片组件)

1. 引言

在鸿蒙(HarmonyOS)应用开发中,UI 组件的复用性直接影响开发效率与代码可维护性。随着应用功能的复杂化,开发者经常需要重复实现具有相似结构但细节不同的 UI 块(如商品卡片、用户信息卡片、设置项卡片),若每次都从头编写代码,不仅会导致冗余,还会增加后期维护成本。

鸿蒙的 ​​ArkUI 框架​​ 提供了强大的自定义组件能力,允许开发者将通用的 UI 结构封装为 ​​带 Props(属性)和 Events(事件)的组件​​,实现“一次封装,多处复用”。本文将以 ​​通用卡片组件​​ 为例,深入讲解如何通过 Props 传递动态数据(如标题、图片、描述),通过 Events 实现交互回调(如点击事件),并封装一个可配置、可扩展的通用组件,适用于商品展示、用户信息、设置项等多种场景。

2. 技术背景

​​2.1 鸿蒙 ArkUI 的组件化思想​​

ArkUI 是鸿蒙生态的原生 UI 开发框架,基于 ​​声明式范式​​(类似 React/Vue),通过组件树构建界面。其核心设计理念包括:

​​组件化​​:将 UI 拆分为独立的、可复用的组件,每个组件封装自身的结构、样式与逻辑;

​​Props 传参​​:父组件通过 ​​属性(Props)​​ 向子组件传递数据(如文本、图片 URL、配置参数);

​​Events 通信​​:子组件通过 ​​事件(Events)​​ 向父组件反馈用户交互(如点击、滑动),实现双向通信;

​​状态管理​​:通过 @State、@Prop 等装饰器管理组件内部/外部状态,支持响应式更新。

​​2.2 通用卡片组件的需求背景​​

在移动应用中,“卡片”是最常见的 UI 模式之一——它通常包含 ​​标题、副标题、图片、描述文本、操作按钮​​ 等元素,用于展示结构化信息(如商品、用户、通知)。不同场景下的卡片虽然结构相似,但细节差异大(如电商卡片需要价格和购买按钮,用户卡片需要头像和昵称),因此需要通过 ​​Props 动态配置内容,通过 Events 处理交互​​,避免重复编写相似代码。

3. 应用使用场景

​​3.1 场景1:电商商品卡片​​

​​需求​​:展示商品图片、名称、价格,支持点击卡片跳转详情页或添加购物车;

​​3.2 场景2:用户信息卡片​​

​​需求​​:显示用户头像、昵称、简介,支持点击头像查看个人资料;

​​3.3 场景3:设置项卡片​​

​​需求​​:呈现设置项标题、描述(如“开启夜间模式”),支持开关切换或点击进入子页面;

​​3.4 场景4:新闻资讯卡片​​

​​需求​​:展示新闻标题、摘要、发布时间,支持点击跳转原文;

4. 不同场景下的详细代码实现

​​4.1 环境准备​​

​​开发工具​​:华为 DevEco Studio(集成 ArkUI 框架);

​​核心概念​​:

​​自定义组件​​:通过 @Component 装饰器定义,封装 UI 结构与逻辑;

​​Props​​:通过 @Prop 或直接参数传递数据(如标题、图片 URL),父组件控制子组件内容;

​​Events​​:通过回调函数(如 onClick?: () => void)向父组件传递交互事件;

​​响应式设计​​:使用 @State 管理组件内部状态(如选中状态),@Prop 接收外部状态;

​​注意事项​​:

自定义组件的样式(如卡片圆角、间距)需通过 @Styles 或内联样式定义,支持灵活配置;

事件的命名需清晰(如 onCardClick、onButtonClick),避免与系统事件冲突。

​​4.2 典型场景:通用卡片组件封装(带 Props/Events)​​

​​4.2.1 代码实现(ArkTS)​​

// 通用卡片组件(GenericCard.ets)

@Component

export struct GenericCard {

// Props:通过父组件传递的配置参数(支持可选/必选)

@Prop title: string = '默认标题'; // 卡片标题(必选,默认值)

@Prop subtitle?: string; // 副标题(可选)

@Prop imageUrl?: string; // 图片 URL(可选)

@Prop description?: string; // 描述文本(可选)

@Prop showButton?: boolean = false; // 是否显示操作按钮(可选,默认不显示)

@Prop buttonText?: string = '操作'; // 按钮文本(可选,默认“操作”)

@Prop onCardClick?: () => void; // 卡片点击事件(可选)

@Prop onButtonClick?: () => void; // 按钮点击事件(可选)

// 组件内部状态(示例:按钮选中状态,非必需)

@State isButtonPressed: boolean = false;

build() {

// 卡片容器:使用 Column 垂直布局

Column() {

// 图片区域(如果提供了 imageUrl)

if (this.imageUrl) {

Image(this.imageUrl)

.width('100%')

.height(120)

.objectFit(ImageFit.Cover)

.borderRadius(8)

.margin({ bottom: 12 })

}

// 文本内容区域

Column() {

// 标题(必选)

Text(this.title)

.fontSize(18)

.fontWeight(FontWeight.Bold)

.margin({ bottom: 4 })

// 副标题(可选)

if (this.subtitle) {

Text(this.subtitle)

.fontSize(14)

.fontColor('#666666')

.margin({ bottom: 8 })

}

// 描述文本(可选)

if (this.description) {

Text(this.description)

.fontSize(14)

.fontColor('#999999')

.lineHeight(20)

.margin({ bottom: 12 })

}

}

.alignItems(HorizontalAlign.Start) // 文本左对齐

.layoutWeight(1) // 占满剩余空间

// 操作按钮(如果启用)

if (this.showButton) {

Button(this.buttonText || '操作')

.width('100%')

.height(40)

.fontSize(16)

.backgroundColor(this.isButtonPressed ? '#E0E0E0' : '#007AFF') // 按压状态变色

.fontColor(Color.White)

.borderRadius(6)

.margin({ top: 12 })

.onClick(() => {

this.isButtonPressed = !this.isButtonPressed; // 模拟按压效果

this.onButtonClick?.(); // 触发按钮点击事件(父组件传入的回调)

})

}

}

.width('100%') // 卡片宽度占满父容器

.padding(16) // 内边距

.backgroundColor(Color.White) // 背景色

.borderRadius(12) // 圆角

.shadow({ // 阴影效果(提升层次感)

radius: 4,

color: '#00000010',

offsetX: 0,

offsetY: 2

})

.onClick(() => {

this.onCardClick?.(); // 触发卡片点击事件(父组件传入的回调)

})

}

}

​​4.2.2 原理解释​​

​​Props 传参​​:父组件通过 @Prop 装饰的属性(如 title、imageUrl、onCardClick)向子组件传递数据与交互逻辑;

​​动态渲染​​:通过条件判断(如 if (this.imageUrl))控制图片、副标题、描述文本、按钮的显示/隐藏,适配不同场景需求;

​​事件回调​​:子组件通过 this.onCardClick?.() 和 this.onButtonClick?.() 触发父组件传入的回调函数,实现交互反馈(如跳转页面、提交表单);

​​样式配置​​:卡片的圆角、阴影、内边距等样式通过链式调用(如 .borderRadius(12))定义,支持灵活调整;

​​状态管理​​:按钮的按压状态(isButtonPressed)通过 @State 管理,实现视觉反馈(如颜色变化)。

​​4.3 典型场景1:电商商品卡片(使用通用组件)​​

​​4.3.1 代码实现(父组件调用)​​

// 商品列表页面(ProductList.ets)

import { GenericCard } from './GenericCard';

@Entry

@Component

struct ProductList {

build() {

Column() {

// 商品卡片1:带图片、标题、价格、购买按钮

GenericCard({

title: '高端智能手机',

subtitle: '6GB+128GB 全网通',

imageUrl: 'https://example.com/phone.jpg',

description: '骁龙8 Gen2处理器,拍照旗舰',

showButton: true,

buttonText: '加入购物车',

onCardClick: () => {

console.log('点击了商品卡片,跳转详情页');

// 实际项目中可跳转到商品详情页(如 router.pushUrl)

},

onButtonClick: () => {

console.log('点击了购买按钮,添加到购物车');

// 实际项目中可调用购物车逻辑

}

})

// 商品卡片2:仅标题和描述(无图片和按钮)

GenericCard({

title: '限时优惠活动',

description: '全场商品满299减50,活动截止明日',

onCardClick: () => {

console.log('点击了活动卡片,跳转活动页');

}

})

}

.width('100%')

.padding(20)

}

}

​​4.3.2 原理解释​​

​​灵活配置​​:父组件通过传递不同的 Props(如 imageUrl、showButton)控制卡片的显示内容,无需修改通用组件代码;

​​交互解耦​​:点击卡片或按钮的逻辑由父组件通过 onCardClick 和 onButtonClick 回调实现(如跳转页面、调用 API),符合单一职责原则;

​​复用性​​:同一通用组件同时用于商品展示和活动推广,减少重复代码。

​​4.4 典型场景2:用户信息卡片(使用通用组件)​​

​​4.4.1 代码实现(父组件调用)​​

// 用户资料页面(UserProfile.ets)

import { GenericCard } from './GenericCard';

@Entry

@Component

struct UserProfile {

build() {

Column() {

// 用户信息卡片:带头像(通过 imageUrl)、昵称(title)、简介(description)

GenericCard({

title: '张三',

subtitle: '高级开发者',

imageUrl: 'https://example.com/avatar.jpg',

description: '专注于鸿蒙原生应用开发,热爱技术分享',

showButton: true,

buttonText: '查看详情',

onCardClick: () => {

console.log('点击了用户卡片,跳转个人主页');

},

onButtonClick: () => {

console.log('点击了详情按钮,打开用户详情页');

}

})

}

.width('100%')

.padding(20)

}

}

​​4.4.2 原理解释​​

​​场景适配​​:通过传递 imageUrl(头像)、subtitle(职业)、description(简介)等 Props,将通用组件适配为用户信息展示;

​​交互扩展​​:按钮文本(buttonText)自定义,点击事件(onButtonClick)可关联到用户详情页跳转。

5. 原理解释

​​5.1 自定义组件的核心机制​​

鸿蒙 ArkUI 的自定义组件通过以下步骤实现:

​​组件定义​​:使用 @Component 装饰器标记一个结构体(如 GenericCard),内部通过 build() 方法定义 UI 结构;

​​Props 传参​​:父组件通过 @Prop 装饰的属性(如 title、imageUrl)向子组件传递数据,子组件通过 this.属性名 访问;

​​Events 通信​​:子组件通过回调函数(如 onCardClick?: () => void)接收父组件的交互逻辑,内部触发时调用 this.回调函数?.();

​​样式与状态​​:通过链式调用(如 .width('100%'))定义样式,通过 @State 管理组件内部动态状态(如按钮按压效果)。

​​5.2 核心特性总结​​

特性

说明

典型应用场景

​​Props 传参​​

父组件动态配置子组件的内容(如标题、图片、是否显示按钮)

多场景复用(商品/用户/设置卡片)

​​Events 回调​​

子组件向父组件反馈交互事件(如点击卡片、点击按钮)

交互逻辑解耦(跳转页面、提交表单)

​​条件渲染​​

通过 if (this.属性) 控制子元素的显示/隐藏,适配不同配置

灵活布局(有图/无图、有按钮/无按钮)

​​样式配置​​

支持圆角、阴影、内边距等样式链式调用,提升视觉效果

品牌一致性(统一的卡片设计)

​​状态管理​​

通过 @State 管理组件内部状态(如按钮按压),实现微交互

增强用户体验(视觉反馈)

6. 原理流程图及原理解释

​​6.1 通用卡片组件工作流程图​​

graph LR

A[父组件调用 GenericCard] --> B[传递 Props(title/imageUrl/onClick...)]

B --> C[GenericCard 组件接收 Props]

C --> D[根据 Props 动态渲染 UI(图片/文本/按钮)]

D --> E[监听用户交互(点击卡片/按钮)]

E --> F[触发 Events 回调(onCardClick/onButtonClick)]

F --> G[父组件执行对应逻辑(跳转页面/调用 API)]

​​6.2 原理解释​​

​​数据流​​:父组件通过 Props 向子组件传递配置数据(如标题、图片 URL),子组件根据这些数据决定渲染哪些 UI 元素;

​​事件流​​:用户点击卡片或按钮时,子组件通过回调函数通知父组件,父组件执行具体的业务逻辑(如页面跳转、数据提交);

​​解耦设计​​:子组件仅负责 UI 渲染与事件触发,不包含具体业务逻辑(如“加入购物车”的具体实现),符合高内聚低耦合原则。

7. 环境准备

​​7.1 开发与测试环境​​

​​操作系统​​:Windows/macOS/Linux(开发机) + 鸿蒙设备(如华为手机/平板,用于真机测试);

​​开发工具​​:华为 DevEco Studio(集成 ArkUI 框架与组件调试工具);

​​关键配置​​:

项目模板:选择“Empty Ability”模板(支持 ArkUI 组件开发);

组件目录:将通用组件(如 GenericCard.ets)放在 src/main/ets/components/ 目录下,便于复用;

权限要求:无特殊权限(仅 UI 渲染,不涉及硬件/网络)。

​​测试设备​​:建议使用不同分辨率的鸿蒙设备(如手机竖屏/横屏、平板)测试组件的适配性。

​​7.2 兼容性检测代码​​

// 检测当前环境是否支持 ArkUI 组件(示例:验证 @Component 装饰器)

@Component

struct CompatibilityTest {

build() {

Text('ArkUI 组件功能正常')

.fontSize(16)

}

}

​​验证步骤​​:运行页面,观察是否正常显示文本(确认 ArkUI 基础功能可用)。

8. 实际详细应用代码示例(综合案例:电商详情页 + 推荐卡片)

​​8.1 场景描述​​

开发一个鸿蒙版电商应用的商品详情页,包含:

​​商品主卡片​​:展示商品主图、名称、价格,支持点击跳转详情;

​​推荐商品列表​​:底部展示多个推荐商品卡片(使用通用组件),每个卡片支持“加入购物车”操作。

​​8.2 代码实现(ArkTS)​​

// 电商详情页(ProductDetail.ets)

import { GenericCard } from './components/GenericCard';

@Entry

@Component

struct ProductDetail {

// 模拟推荐商品数据

private recommendedProducts: Array<{

id: number;

title: string;

subtitle: string;

imageUrl: string;

price: string;

}> = [

{ id: 1, title: '无线蓝牙耳机', subtitle: '降噪版', imageUrl: 'https://example.com/earphone.jpg', price: '¥299' },

{ id: 2, title: '智能手表', subtitle: '运动版', imageUrl: 'https://example.com/watch.jpg', price: '¥1299' },

{ id: 3, title: '充电宝', subtitle: '20000mAh', imageUrl: 'https://example.com/powerbank.jpg', price: '¥99' }

];

build() {

Scroll() {

// 商品主卡片(使用通用组件,配置为详情页样式)

GenericCard({

title: '高端智能手机',

subtitle: '6GB+128GB 全网通',

imageUrl: 'https://example.com/phone-main.jpg',

description: '骁龙8 Gen2处理器,1亿像素主摄,5000mAh长续航',

showButton: true,

buttonText: '查看详情',

onCardClick: () => {

console.log('点击主卡片,跳转商品详情页');

// 实际项目中调用 router.pushUrl('/detail/123')

},

onButtonClick: () => {

console.log('点击详情按钮,打开详情页');

}

})

// 推荐商品列表(循环渲染通用组件)

Text('为您推荐')

.fontSize(18)

.fontWeight(FontWeight.Medium)

.margin({ top: 30, bottom: 15 })

ForEach(this.recommendedProducts, (product: {

id: number;

title: string;

subtitle: string;

imageUrl: string;

price: string;

}) => {

GenericCard({

title: product.title,

subtitle: product.subtitle,

imageUrl: product.imageUrl,

description: `售价:${product.price}`,

showButton: true,

buttonText: '加入购物车',

onCardClick: () => {

console.log(`点击推荐商品 ${product.id},跳转商品页`);

},

onButtonClick: () => {

console.log(`点击推荐商品 ${product.id} 的购物车按钮`);

}

})

.margin({ bottom: 12 }) // 卡片间距

})

}

.width('100%')

.height('100%')

.padding(20)

}

}

9. 运行结果

​​9.1 通用卡片基础功能​​

父组件通过传递不同的 Props(如 imageUrl、showButton),动态控制子组件的显示内容(有图/无图、有按钮/无按钮);

点击卡片或按钮时,控制台输出对应的交互日志(如“点击了商品卡片”),父组件可扩展为实际业务逻辑。

​​9.2 电商详情页集成​​

商品主卡片展示高清主图与详细描述,点击后跳转详情页;

推荐商品列表通过循环渲染通用组件,每个卡片独立配置标题、图片、价格,点击“加入购物车”触发对应逻辑。

10. 测试步骤及详细代码

​​10.1 基础功能测试​​

​​Props 传参验证​​:修改父组件传递的 title、imageUrl,观察子组件是否实时更新;

​​事件回调测试​​:点击卡片或按钮,检查控制台是否输出正确的交互日志;

​​条件渲染测试​​:隐藏 imageUrl 或 showButton,确认对应 UI 元素不显示。

​​10.2 边界测试​​

​​空数据测试​​:不传递 imageUrl 或 description,验证组件是否正常渲染(仅显示标题和副标题);

​​多语言测试​​:将 title 和 description 改为非中文(如英文),确认文本显示无异常。

11. 部署场景

​​11.1 电商应用​​

​​适用场景​​:商品列表页、推荐商品页、用户个人中心(如订单卡片、优惠券卡片);

​​要求​​:通过 Props 动态配置不同类型卡片的内容,通过 Events 实现跳转、加购等业务逻辑。

​​11.2 社交应用​​

​​适用场景​​:用户动态卡片(如朋友圈)、群聊消息卡片、设置项卡片;

​​要求​​:支持图片、文本、按钮的灵活组合,适配不同交互需求(如点赞、评论)。

12. 疑难解答

​​12.1 问题1:子组件未接收到 Props 数据​​

​​可能原因​​:父组件传递的 Props 名称与子组件定义的 @Prop 属性名不一致(如父组件传 title,子组件定义 @Prop cardTitle);

​​解决方案​​:确保父子组件的 Props 名称一致,或通过文档明确约定属性名。

​​12.2 问题2:事件回调未触发​​

​​可能原因​​:父组件未向子组件传递回调函数(如 onCardClick 未定义),或子组件调用时使用了错误的函数名;

​​解决方案​​:检查父组件是否传递了所有需要的事件回调(如 onCardClick: () => { ... }),子组件调用时使用 this.onCardClick?.()。

​​12.3 问题3:图片无法加载​​

​​可能原因​​:传递的 imageUrl 无效(如 URL 拼写错误、网络不可访问),或未处理图片加载失败的默认状态;

​​解决方案​​:检查图片 URL 的有效性,或在子组件中添加 Image 的 onError 回调(显示默认占位图)。

13. 未来展望

​​13.1 技术趋势​​

​​更强大的 Props 类型​​:未来可能支持复杂对象(如嵌套数据结构)作为 Props,进一步提升组件的配置灵活性;

​​内置动画支持​​:通用组件可能集成鸿蒙的动画 API(如 animateTo),允许通过 Props 控制动画效果(如卡片展开/收起);

​​跨页面复用​​:通过全局组件注册(如 globalThis)实现跨页面的通用卡片复用,减少重复导入。

​​13.2 挑战​​

​​性能优化​​:当循环渲染大量通用组件(如 100+ 推荐商品)时,需关注内存占用与渲染效率(可通过虚拟列表优化);

​​多主题适配​​:不同应用主题(如暗色模式/亮色模式)下,通用组件的样式(如文字颜色、背景色)需动态适配;

​​无障碍支持​​:确保通用组件支持屏幕阅读器(如为图片添加 alt 文本,为按钮添加 aria-label)。

​​14. 总结​​

鸿蒙 ArkUI 的自定义组件封装(带 Props/Events)是提升 UI 开发效率与代码复用性的核心手段。通过 ​​通用卡片组件​​ 的实践,我们验证了:

​​Props 传参​​:允许父组件动态配置子组件的内容(如标题、图片、交互按钮),适配多场景需求;

​​Events 通信​​:实现子组件与父组件的交互解耦(如点击反馈、数据提交),符合单一职责原则;

​​灵活扩展​​:通过条件渲染与状态管理,通用组件可轻松扩展为电商卡片、用户卡片、设置项卡片等多种形态。

掌握自定义组件的开发技巧,不仅是鸿蒙开发的必备技能,更是构建高质量、可维护应用的基石。未来,随着 ArkUI 功能的持续增强(如更强大的动画、主题系统),通用组件的应用场景将更加广泛。

🎁 相关推荐

狼犬品种大全,了解这些你就能看出是哪一种!
🎯 365bet体育在线导航

狼犬品种大全,了解这些你就能看出是哪一种!

📅 10-17 👀 8582
十类制作甜品常用的工具推荐 做点心需要什么工具
🎯 365bet体育在线导航

十类制作甜品常用的工具推荐 做点心需要什么工具

📅 09-29 👀 7434
被打伤了判多久刑事拘留
🎯 365bet体育在线导航

被打伤了判多久刑事拘留

📅 08-14 👀 4496