第8篇:组件化开发——创建可复用组件 鸿蒙中文编程
本文介绍了组件化开发的核心概念和实践方法。主要内容包括: 组件化概念:将界面拆分为独立可复用的组件单元,类似乐高积木的构建方式 组件创建:使用@Component装饰器定义组件结构体,包含build()方法构建界面 数据传递: @Prop实现父组件向子组件的单向数据传递 @Link实现父子组件间的双向数据绑定 事件通信:通过回调函数实现子组件向父组件的事件通知 构建器函数:使用@Builder创建
第8篇:组件化开发——创建可复用组件
本课目标:掌握@Component装饰器和组件封装,能创建可复用组件
**作者:**中文编程倡导者—— 李金雨
预计课时:3课时(135分钟)
难度等级:⭐⭐⭐⭐(高级)
一、开篇引入
1.1 从"重复造轮子"到"拿来就用"
想象你要搭建一个乐高城市:
- 你需要很多房子
- 每个房子都有门、窗、屋顶
- 你不会每次都重新设计,而是用一个"房子模板",复制多份
组件化开发就是这个思想!
1.2 为什么要组件化?
不用组件化的问题:
// 页面A需要用户信息卡片
Column() {
Image(头像)
Text(姓名)
Text(年龄)
}
// 页面B也需要用户信息卡片
Column() {
Image(头像)
Text(姓名)
Text(年龄)
}
// 页面C还需要... 重复写N次!
用组件化的好处:
// 定义一次
@Component
struct 用户卡片 {
// ...
}
// 到处使用
用户卡片()
用户卡片()
用户卡片()
1.3 本课目标
今天我们要学习:
- 什么是组件
- 怎么创建自定义组件
- 组件间怎么传递数据(@Prop)
- 组件怎么发送事件
- 实战:按钮组件、卡片组件、弹窗组件
1.4 预期成果
完成本课后,你能创建这样的组件库:
// 通用按钮
<自定义按钮 文字="确定" 类型="主要" 点击={() => {}} />
// 信息卡片
<信息卡片 标题="xxx" 内容="xxx" 图片="xxx" />
// 确认弹窗
<确认弹窗 显示={true} 标题="提示" 内容="确定删除吗?" />
二、概念讲解
2.1 什么是组件?
定义
组件是界面的独立、可复用的组成部分。
就像乐高积木块,每个组件:
- 有自己的结构和样式
- 可以接收输入(属性)
- 可以发出输出(事件)
- 可以嵌套组合
组件的层次
应用
├── 页面A
│ ├── 头部组件
│ │ ├── Logo组件
│ │ └── 导航组件
│ ├── 内容组件
│ │ ├── 卡片组件 × 3
│ │ └── 列表组件
│ └── 底部组件
└── 页面B
├── 头部组件(复用)
└── ...
2.2 @Component装饰器
创建组件
@Component
struct 组件名字 {
build() {
// 组件的界面代码
}
}
| 部分 | 说明 |
|---|---|
@Component |
装饰器,标记这是一个组件 |
struct |
定义结构体 |
组件名字 |
组件的名称(用大驼峰命名) |
build() |
组件的界面组装方法 |
简单例子:问候组件
@Component
struct 问候组件 {
build() {
Column() {
Text("你好!")
.fontSize(24)
Text("欢迎使用本应用")
.fontSize(14)
.fontColor("#999999")
}
.padding(20)
.backgroundColor("#F5F5F5")
.borderRadius(10)
}
}
// 使用组件
@Entry
@Component
struct 主页面 {
build() {
Column() {
问候组件() // 使用自定义组件
问候组件() // 可以重复使用
}
}
}
2.3 @Prop——父组件向子组件传数据
什么是@Prop?
@Prop 让组件可以接收外部传入的数据。
就像函数参数一样!
基本用法
@Component
struct 用户卡片 {
@Prop 姓名: string // 接收姓名
@Prop 年龄: number // 接收年龄
@Prop 头像: string // 接收头像
build() {
Row({ space: 15 }) {
Text(this.头像)
.fontSize(40)
Column({ space: 5 }) {
Text(this.姓名)
.fontSize(18)
.fontWeight(FontWeight.Bold)
Text(`${this.年龄}岁`)
.fontSize(14)
.fontColor("#999999")
}
}
.padding(15)
.backgroundColor("#FFFFFF")
.borderRadius(10)
}
}
// 使用组件,传入数据
@Entry
@Component
struct 主页面 {
build() {
Column({ space: 10 }) {
用户卡片({
姓名: "张三",
年龄: 15,
头像: "👦"
})
用户卡片({
姓名: "李四",
年龄: 16,
头像: "👧"
})
}
}
}
@Prop的特点
@Component
struct 子组件 {
@Prop 数据: string // 从父组件接收
build() {
Button(this.数据)
.onClick(() => {
// 可以修改本地副本,不会影响父组件
this.数据 = "新值"
})
}
}
@Prop是单向绑定:父→子- 子组件修改
@Prop,不会同步到父组件
2.4 @Link——双向绑定
什么是@Link?
@Link 让父子组件双向同步数据。
父组件变,子组件变;子组件变,父组件也变。
基本用法
@Component
struct 计数器组件 {
@Link 计数: number // 双向绑定
build() {
Row({ space: 20 }) {
Button("-")
.onClick(() => {
this.计数-- // 修改会影响父组件
})
Text(`${this.计数}`)
.fontSize(30)
Button("+")
.onClick(() => {
this.计数++ // 修改会影响父组件
})
}
}
}
// 父组件
@Entry
@Component
struct 主页面 {
@State 总数量: number = 0
build() {
Column() {
Text(`当前总数:${this.总数量}`)
// 使用$符号传递Link
计数器组件({ 计数: $总数量 })
}
}
}
2.5 组件事件——子组件向父组件通信
什么是组件事件?
子组件通过回调函数通知父组件发生了什么事。
基本用法
@Component
struct 按钮组件 {
@Prop 文字: string
点击回调: () => void = () => {} // 定义回调
build() {
Button(this.文字)
.onClick(() => {
this.点击回调() // 触发回调
})
}
}
// 父组件
@Entry
@Component
struct 主页面 {
@State 点击次数: number = 0
build() {
Column() {
Text(`点击了${this.点击次数}次`)
按钮组件({
文字: "点我",
点击回调: () => {
this.点击次数++ // 父组件处理事件
}
})
}
}
}
带参数的事件
@Component
struct 列表项组件 {
@Prop 标题: string
点击回调: (标题: string) => void = () => {}
build() {
Row() {
Text(this.标题)
}
.onClick(() => {
this.点击回调(this.标题) // 传递参数
})
}
}
// 使用
列表项组件({
标题: "项目1",
点击回调: (标题: string) => {
console.log("点击了:" + 标题)
}
})
2.6 @Builder——构建器函数
什么是@Builder?
@Builder 是一种轻量级的组件定义方式,适合简单的、不复用的界面片段。
基本用法
@Entry
@Component
struct 主页面 {
@State 用户名: string = "张三"
build() {
Column() {
// 使用@Builder
this.用户卡片(this.用户名)
}
}
// 定义@Builder
@Builder
用户卡片(名字: string) {
Row() {
Text("👤")
Text(名字)
}
.padding(15)
.backgroundColor("#F5F5F5")
}
}
@Builder vs @Component
| 特性 | @Component | @Builder |
|---|---|---|
| 复用性 | 高(可到处使用) | 低(只在当前组件内) |
| 复杂度 | 适合复杂组件 | 适合简单片段 |
| 状态管理 | 支持@State等 | 不支持 |
| 参数传递 | @Prop/@Link | 直接传参 |
三、动手实践
3.1 基础练习:通用按钮组件
创建一个可复用的按钮组件:
// 按钮类型枚举
enum 按钮类型 {
主要 = "#2196F3",
成功 = "#4CAF50",
警告 = "#FF9800",
危险 = "#F44336",
默认 = "#9E9E9E"
}
@Component
struct 通用按钮 {
@Prop 文字: string = "按钮"
@Prop 类型: string = "主要" // 主要、成功、警告、危险、默认
@Prop 禁用: boolean = false
@Prop 加载中: boolean = false
点击回调: () => void = () => {}
// 获取按钮颜色
获取颜色(): string {
switch (this.类型) {
case "主要": return 按钮类型.主要
case "成功": return 按钮类型.成功
case "警告": return 按钮类型.警告
case "危险": return 按钮类型.危险
default: return 按钮类型.默认
}
}
build() {
Button(this.加载中 ? "加载中..." : this.文字, { type: ButtonType.Capsule })
.width('100%')
.height(48)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.backgroundColor(this.获取颜色())
.enabled(!this.禁用 && !this.加载中)
.opacity(this.禁用 ? 0.5 : 1)
.onClick(() => {
if (!this.禁用 && !this.加载中) {
this.点击回调()
}
})
}
}
// 使用示例
@Entry
@Component
// 完整可运行代码,复制到 Index.ets 即可运行
struct Index {
@State 加载状态: boolean = false
build() {
Column({ space: 15 }) {
Text("通用按钮组件")
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin(20)
通用按钮({
文字: "主要按钮",
类型: "主要",
点击回调: () => console.log("点击了主要按钮")
})
通用按钮({
文字: "成功按钮",
类型: "成功",
点击回调: () => console.log("点击了成功按钮")
})
通用按钮({
文字: "警告按钮",
类型: "警告",
点击回调: () => console.log("点击了警告按钮")
})
通用按钮({
文字: "危险按钮",
类型: "危险",
点击回调: () => console.log("点击了危险按钮")
})
通用按钮({
文字: "禁用按钮",
类型: "主要",
禁用: true
})
通用按钮({
文字: "加载按钮",
类型: "主要",
加载中: this.加载状态,
点击回调: () => {
this.加载状态 = true
setTimeout(() => this.加载状态 = false, 2000)
}
})
}
.width('100%')
.height('100%')
.padding(20)
.backgroundColor('#F5F5F5')
}
}
3.2 进阶练习:信息卡片组件
创建一个可复用的信息卡片组件:
// 定义卡片数据类型
interface 卡片数据 {
标题: string
内容: string
图片?: string
标签?: string
时间?: string
}
@Component
struct 信息卡片 {
@Prop 数据: 卡片数据
点击回调: () => void = () => {}
长按回调: () => void = () => {}
build() {
Column({ space: 10 }) {
// 图片区域(如果有)
if (this.数据.图片) {
Stack({ alignContent: Alignment.TopEnd }) {
Text(this.数据.图片)
.fontSize(60)
.width('100%')
.height(150)
.backgroundColor('#E3F2FD')
.textAlign(TextAlign.Center)
if (this.数据.标签) {
Text(this.数据.标签)
.fontSize(11)
.fontColor('#FFFFFF')
.padding({ left: 8, right: 8, top: 3, bottom: 3 })
.backgroundColor('#FF5722')
.borderRadius(4)
.margin(8)
}
}
}
// 内容区域
Column({ space: 8 }) {
Text(this.数据.标题)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text(this.数据.内容)
.fontSize(14)
.fontColor('#666666')
.maxLines(3)
.textOverflow({ overflow: TextOverflow.Ellipsis })
if (this.数据.时间) {
Text(this.数据.时间)
.fontSize(12)
.fontColor('#999999')
.margin({ top: 5 })
}
}
.width('100%')
.padding(15)
.alignItems(HorizontalAlign.Start)
}
.width('100%')
.backgroundColor('#FFFFFF')
.borderRadius(12)
.shadow({ radius: 6, color: '#10000000' })
.onClick(() => this.点击回调())
.gesture(
LongPressGesture({ duration: 500 })
.onAction(() => this.长按回调())
)
}
}
// 使用示例
@Entry
// 完整可运行代码,复制到 Index.ets 即可运行
@Component
struct Index {
@State 卡片列表: 卡片数据[] = [
{
标题: "ArkTS开发入门教程",
内容: "本教程面向零基础学习者,通过通俗易懂的方式讲解ArkTS开发...",
图片: "📚",
标签: "热门",
时间: "2024-01-15"
},
{
标题: "鸿蒙生态介绍",
内容: "HarmonyOS是华为开发的分布式操作系统,支持多种设备...",
图片: "📱",
时间: "2024-01-14"
},
{
标题: "组件化开发技巧",
内容: "学习如何创建可复用的组件,提高开发效率...",
标签: "新课",
时间: "2024-01-13"
}
]
build() {
Column() {
Text("信息卡片组件")
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin(20)
List({ space: 15 }) {
ForEach(this.卡片列表, (卡片: 卡片数据, 索引: number) => {
ListItem() {
信息卡片({
数据: 卡片,
点击回调: () => {
console.log("点击了卡片:" + 卡片.标题)
},
长按回调: () => {
console.log("长按了卡片:" + 卡片.标题)
}
})
}
})
}
.padding(15)
.layoutWeight(1)
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
}
3.3 挑战练习:弹窗组件
创建一个可复用的弹窗组件:
@Component
struct 确认弹窗 {
@Prop 显示: boolean = false
@Prop 标题: string = "提示"
@Prop 内容: string = ""
@Prop 确认文字: string = "确定"
@Prop 取消文字: string = "取消"
@Prop 显示取消: boolean = true
确认回调: () => void = () => {}
取消回调: () => void = () => {}
build() {
Stack() {
// 遮罩层
Column()
.width('100%')
.height('100%')
.backgroundColor('#80000000')
.onClick(() => {
if (this.显示取消) {
this.取消回调()
}
})
// 弹窗内容
Column({ space: 20 }) {
Text(this.标题)
.fontSize(20)
.fontWeight(FontWeight.Bold)
Text(this.内容)
.fontSize(16)
.fontColor('#666666')
.textAlign(TextAlign.Center)
Row({ space: 15 }) {
if (this.显示取消) {
Button(this.取消文字, { type: ButtonType.Capsule })
.width(100)
.height(40)
.backgroundColor('#F5F5F5')
.fontColor('#666666')
.onClick(() => this.取消回调())
}
Button(this.确认文字, { type: ButtonType.Capsule })
.width(100)
.height(40)
.backgroundColor('#2196F3')
.onClick(() => this.确认回调())
}
}
.width('80%')
.padding(25)
.backgroundColor('#FFFFFF')
.borderRadius(16)
}
.width('100%')
.height('100%')
.visibility(this.显示 ? Visibility.Visible : Visibility.Hidden)
}
}
// 使用示例
// 完整可运行代码,复制到 Index.ets 即可运行
@Entry
@Component
struct Index {
@State 显示弹窗: boolean = false
@State 弹窗标题: string = ""
@State 弹窗内容: string = ""
@State 操作结果: string = ""
build() {
Stack() {
Column({ space: 20 }) {
Text("弹窗组件演示")
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin(20)
Button("删除确认弹窗")
.onClick(() => {
this.弹窗标题 = "确认删除"
this.弹窗内容 = "确定要删除这条记录吗?删除后无法恢复。"
this.显示弹窗 = true
})
Button("提示弹窗")
.onClick(() => {
this.弹窗标题 = "操作成功"
this.弹窗内容 = "您的操作已成功完成!"
this.显示弹窗 = true
})
if (this.操作结果 != "") {
Text(`操作结果:${this.操作结果}`)
.fontSize(16)
.fontColor('#2196F3')
.margin(20)
}
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
// 弹窗组件
确认弹窗({
显示: this.显示弹窗,
标题: this.弹窗标题,
内容: this.弹窗内容,
显示取消: this.弹窗标题 == "确认删除",
确认回调: () => {
this.操作结果 = "点击了确定"
this.显示弹窗 = false
},
取消回调: () => {
this.操作结果 = "点击了取消"
this.显示弹窗 = false
}
})
}
}
}
四、知识总结
4.1 核心概念回顾
- 组件:界面的独立、可复用部分
- @Component:标记自定义组件
- @Prop:父组件向子组件传数据(单向)
- @Link:父子组件双向绑定
- 回调函数:子组件向父组件通信
- @Builder:轻量级构建器
4.2 组件通信方式
| 方式 | 方向 | 用途 | 语法 |
|---|---|---|---|
| @Prop | 父→子 | 传递数据 | @Prop 属性: 类型 |
| @Link | 父↔子 | 双向同步 | @Link 属性: 类型,传递用$属性 |
| 回调函数 | 子→父 | 事件通知 | 回调名: () => void = () => {} |
4.3 组件设计原则
- 单一职责:一个组件只做一件事
- 可复用性:组件应该可以在多个地方使用
- 可配置性:通过@Prop让组件行为可定制
- 可测试性:组件应该可以独立测试
4.4 常见错误提醒
| 错误现象 | 原因 | 解决方法 |
|---|---|---|
| @Prop不生效 | 没传值或类型不匹配 | 检查传递的参数 |
| @Link报错 | 没用$符号传递 | 使用$属性名传递 |
| 回调不触发 | 没绑定回调函数 | 确保传入回调函数 |
| 组件不显示 | 组件名拼写错误 | 检查组件名称 |
五、课后作业
5.1 巩固练习(必做)
练习1:输入框组件
封装一个通用输入框组件:
- 支持placeholder
- 支持密码模式
- 支持验证提示
- 支持清除按钮
练习2:评分组件
封装一个五星评分组件:
- 显示星星(支持半星)
- 支持点击评分
- 支持只读模式
- 显示分数
练习3:标签组件
封装一个标签选择组件:
- 显示多个标签
- 支持单选/多选
- 支持自定义颜色
- 支持删除标签
5.2 创意编程(选做)
创意1:轮播图组件
- 自动轮播
- 支持手动滑动
- 显示指示器
- 支持循环播放
创意2:下拉刷新组件
- 支持下拉刷新
- 显示刷新动画
- 支持上拉加载更多
- 显示加载状态
创意3:步骤条组件
- 显示多个步骤
- 标记当前步骤
- 支持点击跳转
- 显示步骤状态
5.3 下篇预习
下一篇,我们将学习页面跳转,实现多页面应用。预习问题:
- 怎么从一个页面跳转到另一个页面?
- 怎么传递数据到下一个页面?
- 怎么返回上一个页面?
恭喜你完成了第8篇的学习! 🎉
现在你已经掌握了组件化开发,可以创建可复用的界面组件了。记住:组件化让代码更整洁,复用性让开发更高效!
下节课,我们将学习如何实现页面跳转!
更多推荐




所有评论(0)