HarmonyOS ArkUI 状态管理装饰器全景,一张表理清所有状态
状态管理解决什么问题
写界面时最头疼的是"数据变了界面怎么跟着变"。手动更新 UI 代码既繁琐又容易漏。ArkUI 的状态管理就是解决这个问题:你声明数据是"状态",框架在数据变化时自动刷新用到它的组件。
状态管理分两个层次,从组件内部到跨页面共享:
┌─────────────────────────────────────────────┐
│ 应用级状态(跨页面/全局共享) │
│ AppStorage / LocalStorage / PersistentStorage│
├─────────────────────────────────────────────┤
│ 组件间状态(父子组件传递) │
│ @Prop / @Link / @Provide / @Consume │
├─────────────────────────────────────────────┤
│ 组件内状态(单个组件自己的数据) │
│ @State / @State 的复杂类型(@Observed 等) │
└─────────────────────────────────────────────┘
装饰器全景表
先把所有状态管理装饰器列出来,理解各自职责:
| 装饰器 | 作用域 | 数据流向 | 用途 |
|---|---|---|---|
@State | 组件内部 | 单向(状态→UI) | 组件自己的可变状态 |
@Prop | 父子组件 | 单向(父→子) | 父传子,子不能改父的值 |
@Link | 父子组件 | 双向(父↔子) | 父子共享,改任意一边都同步 |
@Provide/@Consume | 跨多层组件 | 双向 | 跨层级共享,免逐层传参 |
@Watch | 状态监听 | - | 监听状态变化执行回调 |
@Observed/@ObjectLink | 类对象 | 双向 | 复杂对象内部属性变化也触发刷新 |
AppStorage | 全局 | 双向 | 全局共享状态,进程级 |
LocalStorage | 页面级 | 双向 | 页面内共享状态 |
PersistentStorage | 全局持久化 | 单向(持久化→内存) | 持久化存储,重启不丢 |
数据流向的可视化
不同装饰器的数据流向不同,这决定了选哪个:
@State: 组件内部,自给自足
[组件]
@Prop: 单向,父 → 子(子修改不影响父)
[父组件] ──复制──> [子组件]
@Link: 双向,父 ↔ 子(任一边改都同步)
[父组件] <────> [子组件]
@Provide/@Consume: 跨层级,跳过多层传递
[祖先组件 @Provide] <────> [深层子组件 @Consume]
AppStorage: 全局,所有组件可读写
[组件A] <── AppStorage ──> [组件B]
一个例子看流程
用购物车的"商品数量加减"理解状态流动:
@Entry
@Component
struct CartPage {
@State quantity: number = 1; // 组件内状态:商品数量
build() {
Column({ space: 16 }) {
Text('当前数量:' + this.quantity)
.fontSize(20)
Row({ space: 12 }) {
Button('-')
.onClick(() => {
if (this.quantity > 1) {
this.quantity--; // 改状态
}
})
Button('+')
.onClick(() => {
this.quantity++; // 改状态
})
}
}
}
}
quantity 用 @State 声明,--/++ 改的是状态,Text 引用状态自动刷新。这个流程是:
点击按钮 → 修改 @State 状态 → 框架检测到变化 → 重新渲染引用该状态的 Text
为什么分这么多装饰器
核心是数据所有权的不同。一个数据归谁管、谁能改、改动要不要同步给别处,决定了用哪个装饰器:
- 数据只归这个组件 →
@State - 数据归父组件,子组件只是展示 →
@Prop - 数据父子和子组件都要能改 →
@Link - 数据跨很多层共享 →
@Provide/@Consume(或 AppStorage) - 数据要全局且持久化 →
AppStorage+PersistentStorage
理解"所有权"这个概念,选装饰器就不容易错。下一篇展开讲组件内状态和父子传递的细节。
状态管理的两个层次如何配合
实际应用里,两层状态是配合使用的:
PersistentStorage(登录令牌、用户偏好,重启不丢)
↓ 加载
AppStorage(当前登录用户、全局配置,进程内共享)
↓ 读取
组件 @State(页面内 UI 状态:输入框内容、选中项)
一个常见的登录流程:
- 用户登录 → 令牌和用户信息存到
PersistentStorage(持久化)+AppStorage(全局) - 各页面通过
AppStorage读取当前用户信息 - 页面内部的输入框、加载状态等用
@State管理 - 退出登录 → 清空
PersistentStorage和AppStorage,所有页面自动更新
这个分层让"全局共享"和"局部 UI 状态"各司其职,不会把所有数据都塞进全局导致混乱。
一个关键心智模型
状态管理要记住的核心是:状态变化 → 框架定位到引用它的组件 → 只刷新那些组件。
这意味着两点:
- 你把数据声明成状态(
@State等),框架才会监听它的变化 - 只有真正"读"了状态的组件才会被刷新,没读的不受影响
所以性能上,状态管理不是"整个页面重渲染",而是"最小范围刷新"。写的时候不用太担心性能,但也要避免在一个大组件里堆太多状态。
几条选型的小小经验
-
能局部就别全局。数据只在单个组件用,用
@State就行,别动不动就塞AppStorage。全局状态多了,谁改了数据都难排查。 -
父子传值分清单向双向。子组件只展示父组件的值用
@Prop,需要子改父也用@Link。用错方向会导致数据不同步的奇怪 bug。 -
跨层传递优先
@Provide/@Consume。中间隔了几层还逐层@Prop传,代码又长又难维护,@Provide/@Consume直接跨层。 -
持久化只存必要数据。
PersistentStorage的读写有开销,只存登录令牌、用户偏好这类必须持久化的数据,别把运行时状态都持久化。
更多推荐

所有评论(0)