
摘要
在做应用开发的时候,我们经常会遇到这样的需求:系统提供的原生组件虽然能用,但总感觉差点意思。比如按钮样式不够个性化、输入框逻辑不够灵活、组件复用不够方便。这时候,自定义组件就是解题思路。在鸿蒙(HarmonyOS, ArkTS 开发)里,实现自定义组件并不复杂,甚至和 React/Vue 这些框架有点类似。你只要会封装 UI 和逻辑,就能把它们做成像系统组件一样随用随取的模块。
引言
随着鸿蒙生态逐渐成熟,开发者不仅要会用系统组件,更要能根据实际场景封装自己的组件。举个例子:
- 如果你在做一个表单应用,那可能需要一个"带错误提示的输入框组件"。
- 如果你在做一个商城类应用,那可能需要一个"带点击效果的商品卡片组件"。
- 如果你在做一个工具类应用,那可能需要一个"支持动态切换状态的按钮"。
这些场景都指向一个关键技能:自定义组件。本文会从最基础的入门讲起,再带你通过实战 Demo 和场景案例把这个技能用熟。
基础概念:怎么写一个自定义组件
在鸿蒙的 ArkTS 开发中,实现一个自定义组件的关键点主要有:
用 @Component
定义组件类 组件的 UI 在 build()
方法里写。
用 @Prop
接收外部参数 类似 Vue 的 props
,父组件传值,子组件接收。
用 @State
管理内部状态 内部逻辑变化会触发 UI 更新。
像系统组件一样使用 在父组件中直接 <MyComponent />
即可。
可运行 Demo:自定义按钮
我们先从一个最简单的按钮组件入门。
定义组件(MyButton.ets
)
ts
@Component
struct MyButton {
// 外部传入的文本
@Prop text: string = "默认按钮";
// 外部传入的点击回调
@Prop onClick: () => void = () => {};
// 内部状态:是否被点击过
@State clicked: boolean = false;
build() {
Button(this.clicked ? "已点击: " + this.text : this.text)
.width('80%')
.height(50)
.backgroundColor(this.clicked ? '#4CAF50' : '#2196F3')
.fontColor('#fff')
.borderRadius(10)
.onClick(() => {
this.clicked = true; // 内部状态更新
this.onClick(); // 触发外部回调
})
}
}
使用组件(Index.ets
)
ts
@Entry
@Component
struct Index {
build() {
Column() {
Text("自定义组件 Demo")
.fontSize(20)
.margin({ bottom: 20 })
// 使用自定义按钮
MyButton({
text: "点我一下",
onClick: () => {
console.log("按钮被点击了!");
}
})
}
.width('100%')
.height('100%')
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
}
}
运行效果:
- 初始状态下,按钮显示"点我一下",背景蓝色。
- 点击按钮后,文字变成"已点击: 点我一下",背景变成绿色。
- 控制台会打印"按钮被点击了!"。
进阶场景应用
自定义组件真正的价值,在于它能适配各种业务需求。下面给你三个典型的场景案例。
场景一:带错误提示的输入框
表单类应用常见的需求:输入框输入错误时要提示用户。
ts
@Component
struct ValidInput {
@Prop placeholder: string = "请输入内容";
@Prop validator: (value: string) => boolean = () => true;
@State value: string = "";
@State error: boolean = false;
build() {
Column() {
TextInput({ placeholder: this.placeholder })
.width('90%')
.height(40)
.border({ width: 1, color: this.error ? '#FF0000' : '#CCC' })
.onChange((val: string) => {
this.value = val;
this.error = !this.validator(val);
})
if (this.error) {
Text("输入格式不正确").fontColor('#FF0000').fontSize(12)
}
}
}
}
使用:
ts
ValidInput({
placeholder: "请输入邮箱",
validator: (val: string) => val.includes("@")
})
场景二:带动画的点赞按钮
很多社交类应用里,点赞按钮点一下会有动画效果。
ts
@Component
struct LikeButton {
@State liked: boolean = false;
build() {
Image(this.liked ? 'like_filled.png' : 'like_empty.png')
.width(40)
.height(40)
.onClick(() => {
animateTo({ duration: 300 }, () => {
this.liked = !this.liked;
})
})
}
}
点击时,图标会在 300 毫秒的动画中切换状态。
场景三:商品卡片组件
电商类应用常见的 UI,可以封装成可复用的组件。
ts
@Component
struct ProductCard {
@Prop title: string = "商品标题";
@Prop price: string = "¥0.00";
@Prop image: string = "default.png";
build() {
Column() {
Image(this.image)
.width(150).height(150)
Text(this.title)
.fontSize(16)
.margin({ top: 5 })
Text(this.price)
.fontSize(14)
.fontColor('#E91E63')
}
.borderRadius(10)
.shadow({ radius: 5, color: '#aaa' })
.padding(10)
}
}
使用:
ts
ProductCard({
title: "鸿蒙定制T恤",
price: "¥99",
image: "tshirt.png"
})
常见问题 QA
Q1: 自定义组件和系统组件有什么区别? A: 系统组件是框架提供的基础能力,自定义组件是开发者封装的"组合能力"。你可以在自定义组件里用系统组件,也可以再嵌套别的自定义组件。
Q2: 如果父组件要修改子组件的状态怎么办? A: 用 @Link
,父子组件可以共享状态变量。
Q3: 多层级传递数据很麻烦怎么办? A: 用 @Provide
和 @Consume
,可以跨层级传递状态,类似 Vue 的 provide/inject。
Q4: 自定义组件能不能写成库复用? A: 可以。你可以把常用组件封装成独立模块,在多个项目里复用。
总结
在鸿蒙开发中,自定义组件是提升开发效率和代码复用率的核心技能。 本文从最基础的"自定义按钮"入门,到进阶的"输入框校验""动画按钮""商品卡片",展示了自定义组件在实际场景中的灵活性。
记住几个关键点:
@Component
定义组件。@Prop
接收父组件传值。@State
处理内部状态。- 搭配
@Link
、@Provide
、@Consume
,能处理更复杂的状态管理场景。
掌握这些后,你在鸿蒙开发里几乎可以封装任何 UI 组件,让项目既简洁又可维护。