【HarmonyOS开发小实践】ArkUI 自定义组件与 Modifier:把界面拆成可复用的单元
ArkUI 自定义组件与 Modifier:把界面拆成可复用的单元
为什么需要自定义组件
页面一复杂,build() 里的代码就越堆越长。同一个 UI 片段(比如商品卡片、用户头像、评分条)在多处重复,每次都要复制粘贴。自定义组件就是解决"复用"和"拆分"问题的——把一组组件封装成一个独立单元,一次定义多次使用。
ArkUI 的自定义能力分层,从易到难:
自定义组合 ← 最常用,把系统组件拼成新组件
↑
自定义扩展(Modifier) ← 给组件扩展可复用属性
↑
自定义节点 ← FrameNode/RenderNode/BuilderNode,操作底层
↑
自定义渲染 ← 最底层,完全接管绘制(XComponent/Canvas/NDK)
自定义组合:@Component
最基础的封装用 @Component + build():
// 定义一个可复用的"评分条"组件
@Component
struct RatingBar {
@Prop score: number; // 评分,0-5
@Prop maxScore: number = 5;
build() {
Row() {
ForEach(Array.from({ length: this.maxScore }, (_, i) => i + 1), (star: number) => {
// 小于等于 score 的星是实心的
Text(star <= this.score ? '★' : '☆')
.fontSize(20)
.fontColor(star <= this.score ? '#FFB400' : '#CCCCCC')
}, (star: number) => star.toString())
}
}
}
// 使用
@Entry
@Component
struct UseRating {
build() {
Column({ space: 16 }) {
RatingBar({ score: 4 }) // 4 星
RatingBar({ score: 5, maxScore: 10 }) // 5 星(满分 10)
}
}
}
一次定义 RatingBar,多处使用,通过 @Prop 传入不同参数。这是自定义组合的核心价值。
自定义组件的参数传递
自定义组件像函数一样接收参数。参数在组件内用 @Prop/@Link 声明,调用时传入:
// 组件定义:声明需要的参数
@Component
struct UserCard {
@Prop name: string; // 用户名
@Prop avatar: Resource; // 头像资源
@Link following: boolean; // 是否关注(双向)
build() {
Row() {
Image(this.avatar)
.width(48).height(48).borderRadius(24)
Column() {
Text(this.name).fontSize(16)
}
.margin({ left: 12 })
Blank()
Button(this.following ? '已关注' : '关注')
.onClick(() => {
this.following = !this.following; // 双向修改
})
}
.padding(12)
}
}
// 使用
@Entry
@Component
struct UseUserCard {
@State followed: boolean = false;
build() {
Column() {
UserCard({
name: '张三',
avatar: $r('app.media.avatar1'),
following: $followed // 注意 $ 前缀(@Link 双向)
})
}
}
}
参数分两类:@Prop 传值(不加 $),@Link 传引用(加 $)。
@Builder 构建函数
@Builder 用于提取可复用的 UI 构建代码,比自定义组件更轻量(不涉及独立状态管理):
@Entry
@Component
struct BuilderDemo {
build() {
Column({ space: 16 }) {
// 复用构建函数
this.sectionTitle('最新消息')
this.sectionTitle('热门推荐')
}
}
// 构建函数:一个带样式的标题
@Builder
sectionTitle(title: string) {
Row() {
Text(title)
.fontSize(18)
.fontWeight(FontWeight.Bold)
Text('更多 >')
.fontSize(12)
.fontColor('#999999')
}
.width('100%')
.padding({ left: 16, right: 16 })
.justifyContent(FlexAlign.SpaceBetween)
}
}
@Builder 适合提取"纯粹的结构 + 样式"片段,不带独立状态。带状态用 @Component,纯复用布局用 @Builder。
@BuilderParam:插槽式传参
@BuilderParam 让父组件往子组件"塞"一段自定义 UI,类似 Vue 的 slot:
// 一个带"插槽"的卡片容器
@Component
struct CardShell {
@BuilderParam content: () => void; // 插槽内容
build() {
Column() {
this.content() // 渲染父组件传入的内容
}
.padding(16)
.backgroundColor('#F5F5F5')
.borderRadius(12)
}
}
// 使用:传入不同的内容
@Entry
@Component
struct UseCardShell {
build() {
Column({ space: 16 }) {
CardShell({ content: () => {
Text('这是第一张卡片的内容')
}})
CardShell({ content: () => {
Button('这是第二张卡片的内容')
}})
}
}
}
@BuilderParam 让容器组件(卡片、弹窗、列表项壳)保持通用,具体内容由调用方决定。这是高级复用的关键。
自定义扩展:Modifier
Modifier 是把一组属性封装成可复用单元,然后像 .fontSize() 一样链式调用:
// 定义一个自定义 Modifier:统一的卡片样式
class CardStyleModifier implements AttributeModifier<ColumnAttribute> {
// 在组件属性应用前或后,注入样式
applyNormalAttribute(instance: ColumnAttribute): void {
instance.backgroundColor('#FFFFFF');
instance.borderRadius(12);
instance.padding(16);
// 阴影等
}
}
// 使用
@Entry
@Component
struct ModifierDemo {
build() {
Column({ space: 16 }) {
Column() {
Text('卡片一')
}
.attributeModifier(new CardStyleModifier()) // 应用自定义 Modifier
Column() {
Text('卡片二')
}
.attributeModifier(new CardStyleModifier()) // 复用
}
.padding(16)
}
}
Modifier 的价值是跨组件的样式复用——一套样式定义,作用在不同组件上。适合统一设计规范的场景(统一的卡片、统一的按钮、统一的间距)。
@Component 复用的是"结构和逻辑",Modifier 复用的是"样式属性",两者可以叠加使用。
组合式复用的小案例
做一个商品列表,综合用自定义组件、@Builder、Modifier:
// 商品卡片(复用结构 + 逻辑)
@Component
struct GoodsCard {
@Prop name: string;
@Prop price: number;
@Prop image: Resource;
build() {
Row() {
Image(this.image).width(80).height(80).borderRadius(8)
Column() {
Text(this.name).fontSize(16)
Text(`¥${this.price}`).fontSize(18).fontColor('#FF6B35')
}
.alignItems(HorizontalAlign.Start)
.margin({ left: 12 })
Blank()
Button('加入购物车').height(32).type(ButtonType.Capsule)
}
.padding(12)
}
}
@Entry
@Component
struct GoodsListPage {
build() {
Column() {
// 用 @Builder 复用标题
this.titleBar('今日推荐')
// 用 @Component 复用商品卡片
GoodsCard({ name: '商品A', price: 19.9, image: $r('app.media.goods1') })
GoodsCard({ name: '商品B', price: 29.9, image: $r('app.media.goods2') })
GoodsCard({ name: '商品C', price: 39.9, image: $r('app.media.goods3') })
}
}
@Builder
titleBar(title: string) {
Text(title).fontSize(20).fontWeight(FontWeight.Bold)
.width('100%').padding(16)
}
}
总结一下下
-
优先用
@Component。大部分复用场景,@Component就够了。别一上来就Modifier、FrameNode,复杂但没必要。 -
带状态用组件,纯布局用 Builder。需要独立状态管理(有
@State、@Prop)用@Component,只是提取一段布局用@Builder,更轻量。 -
@BuilderParam解决"容器不确定内容"。做通用卡片、弹窗、tab 容器时,用@BuilderParam留插槽,比在组件内部写死内容更灵活。 -
组件别太大也别太小。一个组件几百行说明该继续拆,一个组件只包一层
Text说明拆得太碎,都不利于维护。 -
参数命名和类型要清晰。自定义组件的
@Prop参数是组件的"接口",起有意义的名字、明确类型,别的开发者才能正确使用。
更多推荐
所有评论(0)