鸿蒙Tab实战03 - build方法200行怎么优化?AttributeModifier模式实战
build方法200行,样式逻辑和UI结构混在一起。如何实现职责分离,让build方法从200行减少到10行?
一、问题场景:build方法200行,样式逻辑和UI结构混在一起
(本节约3分钟,快速了解问题即可)
1.1 问题的开始
在HarmonyOS UI开发中,我们经常遇到这样的问题:UI组件的build方法变得越来越长,样式逻辑和UI结构混在一起,导致代码难以维护、复用和测试。
1.2 传统方式的痛点
痛点1:build方法过长
typescript
// ❌ build方法变得很长,难以阅读
@ComponentV2
export struct NavigationView {
build() {
Row() {
ForEach(this.tabVM.itemList, (item: NavigationItem, index) => {
Stack() {
List() {
ListItem() {
Image(item.icon)
.width(this.getIconSize(item, index))
.height(this.getIconSize(item, index))
.margin({
top: this.getIconMarginTop(item, index),
bottom: this.getIconMarginBottom(item, index)
})
}
ListItem() {
Text(item.name)
.fontSize(this.getFontSize(item, index))
.fontColor(this.getFontColor(item, index))
.fontWeight(this.getFontWeight(item, index))
.backgroundColor(this.getFontBgColor(item, index))
.padding(this.getTextPadding(item, index))
}
}
.width(this.getListWidth(item, index))
.height(this.getListHeight(item, index))
.backgroundColor(this.getListBgColor(item, index))
.padding({ bottom: this.getListPaddingBottom(item, index) })
.onClick(() => this.tabVM.clickTab(index))
}
.width(this.getStackWidth(item, index))
.height(this.getStackHeight(item, index))
.zIndex(this.tabVM.isActivate(index) ? 1 : 0)
.onAreaChange((o, n) => {
// 复杂的尺寸计算逻辑
})
})
}
.alignSelf(ItemAlign.Start)
.justifyContent(FlexAlign.SpaceBetween)
.constraintSize({ minWidth: '100%' })
.alignItems(VerticalAlign.Bottom)
.align(Alignment.TopStart)
.onAreaChange((o, n) => {
// 复杂的样式计算逻辑
})
.backgroundImage(this.getBackgroundImage())
.backgroundImageSize({ width: '100%', height: '100%' })
}
}
问题:
- build方法超过200行,难以阅读和维护
- 样式逻辑和UI结构混在一起,职责不清
- 每次修改样式都需要在build方法中查找
痛点2:难以复用
typescript
// ❌ 样式逻辑无法复用
// 组件A
Row() {
// ...
}
.alignSelf(ItemAlign.Start)
.justifyContent(FlexAlign.SpaceBetween)
.constraintSize({ minWidth: '100%' })
// 组件B需要相同的样式,只能复制粘贴
Row() {
// ...
}
.alignSelf(ItemAlign.Start)
.justifyContent(FlexAlign.SpaceBetween)
.constraintSize({ minWidth: '100%' })
问题:
- 样式逻辑无法复用,需要复制粘贴
- 修改样式需要在多个地方修改
- 容易出现不一致的问题
痛点3:难以测试
样式逻辑无法独立测试,要测试样式逻辑,必须创建完整的UI组件,测试成本高,难以覆盖所有场景。
痛点4:语法限制
typescript
// ❌ builder属性中只能用三元运算符,复杂条件难以表达
Row() {
// ...
}
.width(
this.shouldStickTop
? this.isActivate(index)
? sz(72) // 激活且吸顶
: sz(64) // 未激活且吸顶
: this.businessType === '内嵌'
? sz(160) // 未吸顶且内嵌
: sz(136) // 未吸顶且独立
) // 多层嵌套,可读性差
.margin({
start: this.shouldStickTop && this.isActivate(index) ? szM(8) : szM(0),
end: this.shouldStickTop && this.isActivate(index) ? szM(8) : szM(0),
}) // 复杂条件用三元运算符表达困难
问题:
- 语法限制:在builder属性中,框架不允许使用if语句,只能用三元运算符
- 可读性差:在多分支时使用多层嵌套的三元运算符难以理解
- 难以维护:复杂条件逻辑难以表达和维护
- 功能受限:无法使用switch-case、循环、函数调用等常规语法
二、实战案例:AttributeModifier模式
2.1 解决方案设计
AttributeModifier是HarmonyOS框架提供的特性,允许我们将样式逻辑从UI组件中分离出来,独立成Modifier类。
核心思路:
- UI组件只负责结构:build方法只关注UI结构
- 样式逻辑独立到Modifier:所有样式逻辑都在Modifier中
- 通过attributeModifier应用:一行代码应用所有样式
- 语法灵活性:Modifier中可以使用if-else、switch-case、循环、函数调用等所有常规语法,不受builder属性的语法限制
2.2 完整实现示例
2.2.1 UI组件(build方法10行)
typescript
// ✅ build方法简洁,只负责UI结构
@ComponentV2
export struct NavigationView {
@Param tabVM: NavigationViewModel = new NavigationViewModel()
build() {
Row() {
ForEach(this.tabVM.itemList, (item: NavigationItem, index) => {
Stack() {
List() {
ListItem() {
Image(item.icon)
}
ListItem() {
Text(item.name)
}
}
.onClick(() => this.tabVM.clickTab(index))
}
.attributeModifier(new StackStyleModifier(this.tabVM, item, index))
})
}
.attributeModifier(new RowStyleModifier(this.tabVM))
}
}
对比:
- 传统方式:build方法200+行,样式逻辑和UI结构混在一起
- AttributeModifier方式:build方法10+行,只负责UI结构
2.2.2 Row样式Modifier
typescript
// Row样式逻辑独立到Modifier
export class RowStyleModifier implements AttributeModifier<RowAttribute> {
constructor(private tabVM: NavigationViewModel) {}
applyNormalAttribute(instance: RowAttribute): void {
// 可以使用if-else、switch-case等所有常规语法
instance.alignSelf(ItemAlign.Start)
instance.justifyContent(FlexAlign.SpaceBetween)
instance.constraintSize({ minWidth: '100%' })
instance.alignItems(VerticalAlign.Bottom)
instance.align(Alignment.TopStart)
// 复杂条件逻辑,不再需要三元运算符
if (this.tabVM.shouldStickTop) {
instance.backgroundImage(this.tabVM.getStickyBackgroundImage())
} else {
instance.backgroundImage(this.tabVM.getNormalBackgroundImage())
}
instance.backgroundImageSize({ width: '100%', height: '100%' })
instance.onAreaChange((o, n) => {
// 复杂的样式计算逻辑
this.tabVM.handleAreaChange(o, n)
})
}
}
2.2.3 Stack样式Modifier
typescript
// Stack样式逻辑独立到Modifier
export class StackStyleModifier implements AttributeModifier<StackAttribute> {
constructor(
private tabVM: NavigationViewModel,
private item: NavigationItem,
private index: number
) {}
applyNormalAttribute(instance: StackAttribute): void {
// 复杂条件逻辑,使用if-else更清晰
if (this.tabVM.shouldStickTop) {
if (this.tabVM.isActivate(this.index)) {
instance.width(sz(72))
instance.height(sz(72))
} else {
instance.width(sz(64))
instance.height(sz(64))
}
} else {
if (this.tabVM.businessType === '内嵌') {
instance.width(sz(160))
instance.height(sz(160))
} else {
instance.width(sz(136))
instance.height(sz(136))
}
}
instance.zIndex(this.tabVM.isActivate(this.index) ? 1 : 0)
instance.onAreaChange((o, n) => {
// 复杂的尺寸计算逻辑
this.tabVM.handleStackAreaChange(this.index, o, n)
})
}
}
2.2.4 List样式Modifier
typescript
// List样式逻辑独立到Modifier
export class ListStyleModifier implements AttributeModifier<ListAttribute> {
constructor(
private tabVM: NavigationViewModel,
private item: NavigationItem,
private index: number
) {}
applyNormalAttribute(instance: ListAttribute): void {
// 使用switch-case处理多种场景
switch (this.tabVM.getListStyleType(this.index)) {
case 'active':
instance.width(sz(72))
instance.height(sz(72))
instance.backgroundColor(Color.Blue)
break
case 'inactive':
instance.width(sz(64))
instance.height(sz(64))
instance.backgroundColor(Color.Gray)
break
default:
instance.width(sz(60))
instance.height(sz(60))
instance.backgroundColor(Color.Transparent)
}
instance.padding({ bottom: this.tabVM.getListPaddingBottom(this.index) })
}
}
2.3 实际效果
使用AttributeModifier模式后,我们获得了以下效果:
- build方法从200+行减少到10+行:代码量减少95%
- 职责分离清晰:UI结构、样式逻辑、业务逻辑分离
- 样式逻辑可复用:Modifier可以在多个组件中复用
- 样式逻辑可测试:Modifier可以独立测试,无需创建完整UI组件
- 语法灵活性:Modifier中可以使用if-else、switch-case、循环、函数调用等所有常规语法
三、理论分析:职责分离的架构基础
3.1 职责分离的理论价值
单一职责原则:
- 每个类或模块应该只有一个职责
- UI组件只负责UI结构
- Modifier只负责样式逻辑
- ViewModel只负责业务逻辑
开闭原则:
- 对扩展开放,对修改关闭
- 新增样式只需新增Modifier,无需修改UI组件
- 修改样式只需修改Modifier,无需修改UI组件
3.2 声明式UI的架构基础
声明式UI的特点:
- 描述"是什么",而非"如何做"
- UI结构清晰,样式逻辑独立
- 状态变化自动更新UI
职责分离如何支撑声明式编程:
- UI组件:声明UI结构,描述"是什么"
- Modifier:声明样式逻辑,描述"如何样式化"
- ViewModel:管理状态,描述"数据是什么"
3.3 架构价值
可维护性提升:
- 样式逻辑独立,修改不影响UI结构
- 代码结构清晰,易于理解和维护
可复用性提升:
- Modifier可以在多个组件中复用
- 样式逻辑可以独立封装和复用
可测试性提升:
- Modifier可以独立测试,无需创建完整UI组件
- 测试成本低,易于覆盖所有场景
四、整体架构图
AttributeModifier模式的整体架构如下:
业务逻辑和状态] end subgraph Modifier层 C[RowStyleModifier
Row样式逻辑] D[StackStyleModifier
Stack样式逻辑] E[ListStyleModifier
List样式逻辑] end subgraph UI组件层 A[NavigationView组件] B[build方法
只负责UI结构] A --> B end %% ViewModel到Modifier的连接 F -->|状态变化| C F -->|状态变化| D F -->|状态变化| E %% Modifier到UI组件的连接 C -->|attributeModifier| B D -->|attributeModifier| B E -->|attributeModifier| B %% 可选:添加UI到ViewModel的反向数据流 B -.->|用户交互事件| F
架构说明:
- UI组件层:只负责UI结构,build方法简洁
- Modifier层:负责所有样式逻辑,可复用、可测试
- ViewModel层:负责业务逻辑和状态管理
五、方案能力边界
根据样式复杂度、复用需求、build方法长度等因素,选择合适的方案
考虑使用AttributeModifier的情形:
UI属性存在多个复杂逻辑时
项目中多个组件有共同属性
需要复用和共同维护时
属性数量过多导致build函数臃肿时,例如50行以上
六、总结提升:职责分离的价值
(本节约1分钟,快速总结)
6.1 实践验证理论
在实际项目中,AttributeModifier模式带来了显著的效果:
- build方法从200+行减少到10+行:代码量减少95%
- 职责分离清晰:UI结构、样式逻辑、业务逻辑分离
- 样式逻辑可复用:Modifier可以在多个组件中复用
- 样式逻辑可测试:Modifier可以独立测试
这些实际效果验证了职责分离架构的理论价值。
6.2 理论指导实践
职责分离理论为实际项目提供了架构方向:
- 单一职责原则:每个类或模块应该只有一个职责
- 开闭原则:对扩展开放,对修改关闭
- 声明式UI:描述"是什么",而非"如何做"
6.3 相互印证
AttributeModifier模式是职责分离的实现方式:
- 职责分离:UI组件、Modifier、ViewModel各司其职
- 声明式UI:职责分离支撑声明式编程
- 架构价值:提升可维护性、可复用性、可测试性
七、下一章预告
在下一章中,我们将探讨:全局工具类架构设计:单例模式的生命周期管理。
通过完整的单例模式设计,我们将看到如何解决生命周期管理、初始化分离、响应式更新等基础设施层面的架构挑战。
说明 :本文中的代码示例均经过脱敏处理,部分实现细节已简化,主要用于演示设计思路和架构理念。代码结构符合HarmonyOS规范,但实际使用时请根据具体业务场景调整,并参考HarmonyOS官方文档和最佳实践。