ArkUI 组件复用与数据通信
在鸿蒙ArkUI声明式开发体系中,组件复用与父子组件通信是从“基础页面编写”进阶到“工程化项目开发”的核心分水岭。规范化开发通过复用机制精简代码、统一UI风格,并通过组件通信实现页面数据驱动更新。
一、前言:为什么需要组件复用?
在开发电商首页、购物车、列表卡片等复杂页面时,页面中存在大量重复的样式、重复的UI结构、统一的交互逻辑。如果全部手写,会出现代码冗余、样式不统一、后期维护成本极高的问题。
ArkUI 提供了一套完整的复用方案:样式复用、UI结构复用、自定义组件复用,配合父子组件数据通信机制,能够实现高内聚、低耦合的前端开发模式,也是企业级鸿蒙项目模块化开发的基础。
二、三大复用装饰器对比与实战用法
(1)@Styles:全局通用样式复用
@Styles 用于抽取多个组件通用的公共样式,仅复用样式属性,不复用UI结构。适用于全局统一的字体、颜色、边距、对齐等基础样式。
@Styles 只能封装布局容器(Row/Column/Flex) 的链式样式,不能封装 Text、Button 等内部组件样式。 适合统一容器的宽、内边距、背景、对齐等。
核心特点:无组件绑定、全局可调用、轻量化、仅样式复用。
@Entry
@Component
struct Index {
build() {
Column({ space: 20 }) {
Row() {
Text("商品A")
Text("数量:1")
}
.boxStyle()
Row() {
Text("商品B")
Text("数量:5")
}
.boxStyle()
}
.pageStyle()
}
}
@Styles function boxStyle() {
.width('100%')
.padding(15)
.backgroundColor('#f5f5f5')
.borderRadius(10)
}
@Styles function pageStyle() {
.width('100%')
.padding(20)
}
(2)@Extend:专属组件样式扩展
与 @Styles 不同,@Extend 绑定指定原生组件,只能作用于目标组件,可以扩展原生组件属性,甚至绑定专属交互逻辑。
核心特点:组件专属、可扩展原生能力、比 Styles 功能性更强。
适用场景:自定义专属Text文本、专属Button按钮,实现全局统一的定制化组件样式。
@Extend(Text) function textBase() {
.fontSize(18)
.fontColor('#333')
.margin({ bottom: 8 })
}
@Extend(Button) function btnBase() {
.width(140)
.height(42)
.borderRadius(6)
.fontSize(17)
}
@Entry
@Component
struct Index {
@State num: number = 0
build() {
Column({ space: 25 }) {
Text('计数器演示')
.textBase()
Text(`当前数值:${this.num}`)
.textBase()
Row({ space: 15 }) {
Button('-')
.btnBase()
.onClick(() => {
if (this.num > 0) this.num--
})
Button('+')
.btnBase()
.onClick(() => {
this.num++
})
}
}
.padding(25)
}
}
(3)@Builder:UI结构片段复用
@Builder 是 ArkUI 中复用UI结构的核心方案,可以封装一段重复的组件结构,支持传参、支持动态数据渲染,是页面局部复用的最优解。
同时搭配 @BuilderParam 可以实现父组件向子组件注入自定义UI片段,实现高灵活度的组件插槽效果,极大提升组件的复用性和扩展性。
适用场景:列表卡片、通用标题栏、通用空数据页面、重复弹窗结构。
@Entry
@Component
struct Index {
@State msg: string = '学习ArkUI'
@Builder TitleText() {
Text('我是标题')
.fontSize(22)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 10 })
}
@Builder GoodsCard(name: string, count: number) {
Row() {
Text(`商品:${name}`)
Text(`数量:${count}`)
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.padding(15)
.backgroundColor('#f5f5f5')
.borderRadius(8)
.margin({ bottom: 12 })
}
build() {
Column() {
this.TitleText()
// 调用带参Builder,传入不同数据
this.GoodsCard('笔记本', 2)
this.GoodsCard('鼠标', 5)
this.GoodsCard(this.msg, 1)
Button('修改文字')
.onClick(() => {
this.msg = '鸿蒙组件学习'
})
}
.padding(20)
}
}
三、自定义组件与父子组件通信原理
单纯的结构复用只能解决代码冗余问题,想要实现数据驱动UI更新,必须依靠组件状态通信机制。在模块化开发中,页面会拆分为父组件与多个子组件,数据由父组件统一管理,子组件负责展示,通过单向数据流实现状态统一管理。
(1)核心通信装饰器:@Prop 单向传值
@Prop 用于实现父组件向子组件单向数据传递,是项目中最常用的组件通信方式。父组件使用 @State 定义状态变量,通过传参传递给子组件的 @Prop 变量。
核心机制:父组件数据更新 → 自动同步子组件 → 子组件UI自动刷新,子组件无法修改父组件数据,保证数据流单向可控,避免状态混乱。
@Component
struct NumberCount{
@Prop num:number = 1
subFn = () => {}
addFn = () => {}
build(){
Row({space : 10}){
Button('-')
.onClick(() => {
this.subFn()
})
Text(this.num.toString())
Button('+')
.onClick(() => {
this.addFn()
})
}
.padding(10)
}
}
@Entry
@Component
@Preview
struct Test{
@State num1:number = 5
@State num2:number = 3
build(){
Column(){
Row({space : 5}){
Text('茄子')
NumberCount({
num:this.num1,
subFn: () => this.num1--,
addFn: () => this.num1++
})
}
Row({space : 5}){
Text('香蕉')
NumberCount({
num:this.num2,
subFn: () => this.num2--,
addFn: () => this.num2++
})
}
}
.padding(20)
}
}
(2)@State 与 @Prop 职责区分
很多新手误区:子组件定义大量 @State 状态。实际工程规范中:
@State:仅用于组件内部私有状态,不对外暴露
@Prop:用于接收父组件传递的业务数据,只读、可响应更新
该机制保证了数据来源唯一,是复杂页面状态不乱、易于维护的关键。
(3)@Link 双向绑定
当子组件需要修改父组件的数据时(如表单输入、开关切换),使用 @Link 实现双向绑定。
需要注意的是:@Link修饰的变量是不能赋初始值的,@Prop是可以的。
@Entry
@Component
struct Index {
@State num: number = 5
build() {
Column({ space: 30 }) {
Text(`父组件当前数值:${this.num}`)
.fontSize(22)
.fontWeight(FontWeight.Medium)
Counter1({
count: this.num,
min: 0,
max: 10
})
}
.width('100%')
.padding(30)
}
}
// 子组件:计数器
@Component
struct Counter1 {
@Link count: number
@Prop min: number = 0
@Prop max: number = 10
build() {
Row({ space: 12 }) {
Button('-')
.width(36)
.height(36)
.borderRadius(18)
.fontSize(20)
.onClick(() => {
if (this.count > this.min) {
this.count--
}
})
Text(`${this.count}`)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.width(36)
.textAlign(TextAlign.Center)
Button('+')
.width(36)
.height(36)
.borderRadius(18)
.fontSize(20)
.onClick(() => {
if (this.count < this.max) {
this.count++
}
})
}
.height(48)
.alignItems(VerticalAlign.Center)
}
}
(4)@Provide 与 @Consume:跨层级传递
当组件嵌套层级较深时,使用 @Provide 和 @Consume 实现跨组件传递,避免逐层传递的繁琐。
这个也是双向连接的,就是互相之间可以修改数值。
class CountData {
num: number = 0
}
@Component
struct ParentRoot {
@Provide data: CountData = new CountData()
build() {
Column({ space: 20 }) {
Text(`顶层数字:${this.data.num}`)
.fontSize(22)
MiddleBox()
}
.padding(30)
}
}
@Component
struct MiddleBox {
build() {
Column({ space: 10 }) {
Text("中间组件")
ChildBox()
}
}
}
@Component
struct ChildBox {
@Consume data: CountData
build() {
Row({ space: 10 }) {
Button("-")
.onClick(() => {
if (this.data.num > 0) {
this.data.num--
}
})
Text(`${this.data.num}`)
.fontSize(20)
Button("+")
.onClick(() => {
this.data.num++
})
}
}
}
@Entry
@Component
struct Index {
build() {
Column() {
ParentRoot()
}
}
}
四、总结
三者复用方案各司其职:样式复用统一界面、Builder 结构复用精简代码、自定义组件+Prop和Link通信实现业务解耦。掌握这套体系后,已经具备开发中大型电商级复杂页面的能力,摆脱重复编码,写出规范、简洁、可维护的鸿蒙代码。
更多推荐



所有评论(0)