状态管理解决什么问题

写界面时最头疼的是"数据变了界面怎么跟着变"。手动更新 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 状态:输入框内容、选中项)

一个常见的登录流程:

  1. 用户登录 → 令牌和用户信息存到 PersistentStorage(持久化)+ AppStorage(全局)
  2. 各页面通过 AppStorage 读取当前用户信息
  3. 页面内部的输入框、加载状态等用 @State 管理
  4. 退出登录 → 清空 PersistentStorageAppStorage,所有页面自动更新

这个分层让"全局共享"和"局部 UI 状态"各司其职,不会把所有数据都塞进全局导致混乱。

一个关键心智模型

状态管理要记住的核心是:状态变化 → 框架定位到引用它的组件 → 只刷新那些组件

这意味着两点:

  1. 你把数据声明成状态(@State 等),框架才会监听它的变化
  2. 只有真正"读"了状态的组件才会被刷新,没读的不受影响

所以性能上,状态管理不是"整个页面重渲染",而是"最小范围刷新"。写的时候不用太担心性能,但也要避免在一个大组件里堆太多状态。

几条选型的小小经验

  1. 能局部就别全局。数据只在单个组件用,用 @State 就行,别动不动就塞 AppStorage。全局状态多了,谁改了数据都难排查。

  2. 父子传值分清单向双向。子组件只展示父组件的值用 @Prop,需要子改父也用 @Link。用错方向会导致数据不同步的奇怪 bug。

  3. 跨层传递优先 @Provide/@Consume。中间隔了几层还逐层 @Prop 传,代码又长又难维护,@Provide/@Consume 直接跨层。

  4. 持久化只存必要数据PersistentStorage 的读写有开销,只存登录令牌、用户偏好这类必须持久化的数据,别把运行时状态都持久化。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐