本文同步发表于我的微信公众号,微信搜索 程语新视界 即可关注,每个工作日都有文章更新
一、概述
在HarmonyOS的ArkUI声明式开发范式中,采用MVVM(Model-View-ViewModel)架构模式,UI是应用状态的函数,状态变化驱动UI更新。ArkUI提供了一系列装饰器(如@State、@Prop、@Link、@Provide、@Consume等)来实现数据与视图的绑定。不合理的状态管理会导致UI不一致、不必要的渲染刷新等问题。
二、合理选择装饰器
1. 避免不必要的状态变量
状态变量的管理有额外开销,应仅在必要时使用。普通变量用状态装饰器标记可能导致性能下降。
反例1:冗余的状态变量
@Observed
class UserPosition {
x: number = 20;
}
@Component
struct UserComponent {
// 错误:positionObj未关联任何UI组件,不应定义为状态变量
@State positionObj: UserPosition = new UserPosition();
// 错误:buttonLabel未关联任何UI组件,不应定义为状态变量
@State buttonLabel: string = '点击我';
build() {
// 这两个状态变量未被使用
}
}
反例2:只读的状态变量
@Observed
class UserPosition {
x: number = 20;
}
@Component
struct UserComponent {
@State positionObj: UserPosition = new UserPosition();
@State buttonLabel: string = '点击我'; // 只有读取操作
build() {
Column() {
Button(this.buttonLabel) // 仅读取值,无修改操作
}
}
}
正例:合理使用状态变量
@Observed
class UserPosition {
x: number = 20;
}
@Component
struct OptimizedComponent {
@State positionObj: UserPosition = new UserPosition(); // 有读写操作,使用状态变量
buttonLabel: string = '点击我'; // 仅读取操作,使用普通变量
build() {
Column() {
Button(this.buttonLabel)
.onClick(() => {
animateTo({ duration: 50 }, () => {
// 修改状态变量,驱动UI更新
this.positionObj.x = this.positionObj.x + 50;
})
})
}
.translate({ x: this.positionObj.x })
}
}
2. 建议使用临时变量替换状态变量
状态变量变化会触发UI渲染,通过临时变量计算可减少不必要的渲染次数。
反例:直接操作状态变量
@Component
struct LoggerComponent {
@State logContent: string = '';
// 直接多次修改状态变量
appendLog(newLog: string) {
this.logContent += newLog;
this.logContent += ';';
this.logContent += '<br/>';
}
build() {
Column() {
Button('添加日志')
.onClick(() => {
this.appendLog('用户操作记录');
})
}
}
}
正例:使用临时变量优化
@Entry
@Component
struct OptimizedLogger {
@State logContent: string = '';
// 使用临时变量,只修改一次状态变量
appendLog(newLog: string) {
let tempContent = this.logContent;
tempContent += newLog;
tempContent += ';';
tempContent += '<br/>';
this.logContent = tempContent; // 单次赋值
}
build() {
Column() {
Button('添加日志')
.onClick(() => {
this.appendLog('用户操作记录');
})
}
}
}
三、最小化状态共享范围
1. 组件内独享的状态
使用@State装饰器,状态生命周期与组件同步,修改只触发当前组件重渲染。
@Component
struct ThemeItem {
@State isSelected: boolean = false; // 组件内独享状态
build() {
Button(this.isSelected ? '已选' : '未选')
.onClick(() => {
this.isSelected = !this.isSelected; // 只更新当前组件
})
}
}
2. 组件间共享的状态
按共享范围从小到大选择合适的装饰器:
父子组件共享:使用@Prop和@Link
// 父组件
@Entry
@Component
struct ParentView {
@State isLoading: boolean = false;
build() {
Column() {
ChildComponent({ loading: this.isLoading })
AnotherChild({ loading: $isLoading })
}
}
}
// 子组件
@Component
struct ChildComponent {
@Prop loading: boolean; // 单向同步
build() {
Text(this.loading ? '加载中...' : '完成')
}
}
@Component
struct AnotherChild {
@Link loading: boolean; // 双向同步
build() {
Button('切换状态')
.onClick(() => {
this.loading = !this.loading;
})
}
}
跨组件层级共享:使用@Provide和@Consume或@Provider和@Consumer
四、使用监听和订阅精准控制组件刷新
当多个组件依赖对象中的不同属性时,直接关联该对象会出现改变任一属性所有组件都刷新的现象。可通过以下策略精准控制组件刷新:
1. 属性拆分
将包含多个属性的类拆分为多个单一属性的状态类
反例:所有组件都刷新
@Observed
class UserProfile {
name: string = '';
age: number = 0;
email: string = '';
}
@Component
struct UserCard {
@ObjectLink profile: UserProfile;
build() {
// 任何属性变化都会导致整个组件刷新
}
}
正例:精准控制刷新
@Observed
class UserName {
value: string = '';
}
@Observed
class UserAge {
value: number = 0;
}
@Observed
class UserEmail {
value: string = '';
}
@Component
struct UserNameCard {
@ObjectLink name: UserName;
// 只关注name变化
}
@Component
struct UserAgeCard {
@ObjectLink age: UserAge;
// 只关注age变化
}
2. 发布订阅模式
当组件关系复杂或跨越层级过多时,推荐使用EventHub或者Emitter自定义事件发布订阅的方式来精确通知特定组件。
五、装饰器选择策略
- @State:组件内部私有状态,不需要与父组件或后代组件共享
- @Prop:父子组件单向同步,父组件到子组件
- @Link:父子组件双向同步
- @Provide/@Consume:跨组件层级双向同步(状态管理V1)
- @Provider/@Consumer:跨组件层级双向同步(状态管理V2,API 12+)
- LocalStorage:页面内跨组件树状态共享
- AppStorage:应用全局状态共享
六、建议
- 使用Code Linter工具:检测冗余状态变量和未变化状态变量
- 复杂计算使用临时变量:减少状态变量直接操作次数
- 合理划分状态范围:避免不必要的全局状态,按需共享
- 精准控制刷新:对于集合数据,使用细粒度监听策略
- 避免在键值中包含索引:在ForEach循环中,避免使用索引作为key的一部分
- 使用防抖节流:对于频繁更新的状态,考虑使用防抖或节流机制
1478

被折叠的 条评论
为什么被折叠?



