本篇看点
- 阅读目标:理解
@State/@Prop 背后的状态所有权,判断状态该放在哪里。
- 核心问题:状态管理第一步不是选装饰器,而是判断谁拥有这份状态。
- 对比迁移:对应 Flutter 状态上提和 SwiftUI
@State / UIKit configure model。
前言
HarmonyOS 学到状态管理时,很多人会把 @State 理解成 Flutter 的 setState,把 @Prop 理解成构造参数。这个类比能入门,但如果一直停在这里,后面一定会在刷新边界上吃亏。
这一篇只讲两个最基础的装饰器:@State 和 @Prop。重点不是背语法,而是理解状态所有权。
一. @State:组件自己的可变状态
@State 表示当前组件持有这份状态,并且这份状态变化会触发对应 UI 更新。
@State count: number = 0;
build() {
Column() {
Text(`点击次数:${this.count}`)
Button('加一')
.onClick(() => {
this.count += 1;
})
}
}
这很像 Flutter 的 StatefulWidget 内部字段,也像 SwiftUI 的 @State。差别在于 ArkUI 会通过装饰器明确声明哪些字段参与响应式更新。
二. @Prop:父组件传给子组件的只读输入
子组件只展示父组件传来的值,可以用 @Prop:
@Component
struct AlbumTitle {
@Prop title: string;
build() {
Text(this.title)
.fontSize(16)
.fontWeight(FontWeight.Bold)
}
}
父组件这样使用:
AlbumTitle({ title: this.album.title })
这里的关系很清楚:父组件拥有 album,子组件只是消费 title。
Flutter 里构造函数参数也是这个模式;UIKit 里则常见 cell.configure(model)。ArkUI 的 @Prop 更强调响应式输入:父组件传入值变化,子组件可以跟着更新,修改入口仍然留在父组件或明确的状态持有者那里。
三. 状态应该放在哪里
判断状态归属,可以问三个问题:
- 只有这个组件用吗?放
@State。
- 父组件和多个子组件都要用吗?放父组件,通过
@Prop 下发。
- 很多页面都要用吗?进入 Store 或应用级控制器。
比如一个课程卡片是否展开:
@State expanded: boolean = false;
这个状态只影响卡片内部,放卡片自己就好。
但一个播放器当前曲目:
currentTrack: Track | undefined
它影响迷你播放器、详情页、播放列表、锁屏控制,更适合进入全局播放器 Store,而不是留在某个页面组件里。
四. 正向传值不是把整个对象都丢下去
很多人写子组件时喜欢直接传整个 model:
AlbumRow({ album: album })
这不一定错,但要看子组件真的需要什么。如果子组件只展示标题和封面,传整个 album 会让子组件知道太多。
AlbumRow({
title: album.title,
cover: album.cover,
playCount: album.playCount
})
在真实项目里,我一般这样取舍:业务组件可以传业务 model,基础组件尽量传最小字段。比如 AlbumCard 可以认识 Album,但 CoverArt 最好只认识图片地址、尺寸和圆角。
五. 刷新边界从这里开始
@State 改变的是当前组件的状态。如果你把整个页面的数据都放在页面根组件里,任何字段变化都可能让大块 UI 重新计算。
所以状态不用都堆到首页。状态离使用它的地方越近,刷新边界越小,阅读成本也越低。
Flutter 里同样如此:把所有状态都放在一个大 StatefulWidget,然后 setState 整页刷新,很快就会卡。iOS 里 tableView.reloadData() 全量刷也一样粗。
六. 实践经验
第一,把 @Prop 当成本地状态去改,导致状态来源混乱。
第二,父组件把太多字段传给子组件,子组件变成业务垃圾桶。
第三,页面根组件持有所有 UI 状态,导致局部刷新做不起来。
第四,为了省事把全局单例当状态中心,最后很难测试。
小结
@State/@Prop 是 HarmonyOS 状态管理的起点。它们真正教你的不是语法,而是状态所有权:谁拥有,谁修改,谁展示。
下一篇继续讲反向传值、双向绑定和跨层传值,也就是 callback、@Link、@Provide/@Consume 这些更容易被误用的能力。
今日练习
- 给一个卡片组件区分展示参数和内部展开状态。
- 把只用于展示的字段通过
@Prop 传给子组件。
- 选一个全局状态,比如当前播放曲目,说明它为什么不该放在普通页面里。