HarmonyOS 状态管理 V1 —— @State、@Prop、@Link 与 AppStorage

本文献给:

已掌握基础 UI 构建、能独立完成静态页面布局的鸿蒙开发者。本文将深入讲解 HarmonyOS ArkUI 中的状态管理 V1 版本,聚焦 @State@Prop@Link 三者的使用方式、区别与适用场景,并介绍 AppStorage 全局状态存储的基本用法,帮助你构建响应式、可交互的应用界面。

你将学到:

  1. @State 装饰器的用法与组件内部状态管理
  2. @Prop 实现父子组件间的单向数据传递
  3. @Link 实现父子组件间的双向数据同步
  4. @State@Prop@Link 的区别与选型策略
  5. AppStorage 的基本 API 与跨页面/跨组件共享状态
  6. 实际场景中状态管理的综合应用

目录

  • [一、@State ------ 组件内部状态](#一、@State —— 组件内部状态)
    • [1.1 基本语法](#1.1 基本语法)
    • [1.2 @State 的特点](#1.2 @State 的特点)
    • [1.3 注意事项](#1.3 注意事项)
  • [二、@Prop ------ 父到子单向传递](#二、@Prop —— 父到子单向传递)
    • [2.1 基本语法](#2.1 基本语法)
    • [2.2 @Prop 的特点](#2.2 @Prop 的特点)
    • [2.3 注意事项](#2.3 注意事项)
  • [三、@Link ------ 父子双向同步](#三、@Link —— 父子双向同步)
    • [3.1 基本语法](#3.1 基本语法)
    • [3.2 @Link 的特点](#3.2 @Link 的特点)
    • [3.3 注意事项](#3.3 注意事项)
  • [四、@State、@Prop、@Link 三者对比与选型](#四、@State、@Prop、@Link 三者对比与选型)
  • [五、AppStorage ------ 全局状态存储](#五、AppStorage —— 全局状态存储)
    • [5.1 基本 API](#5.1 基本 API)
    • [5.2 存储与使用](#5.2 存储与使用)
    • [5.3 应用场景](#5.3 应用场景)
  • [六、综合示例 ------ 计数器与用户信息联动](#六、综合示例 —— 计数器与用户信息联动)
  • 七、常见错误与注意事项
    • [7.1 直接修改 @State 对象的属性不触发更新](#7.1 直接修改 @State 对象的属性不触发更新)
    • [7.2 @Prop 变量被当成双向绑定使用](#7.2 @Prop 变量被当成双向绑定使用)
    • [7.3 @Link 使用时忘记在父组件中使用 `\` 传引用](#7.3 @Link 使用时忘记在父组件中使用 `` 传引用)
    • [7.4 AppStorage 未初始化就访问](#7.4 AppStorage 未初始化就访问)
    • [7.5 滥用 AppStorage](#7.5 滥用 AppStorage)
  • 八、小结

一、@State ------ 组件内部状态

@State 用于声明组件内部的私有状态变量。当被 @State 装饰的变量值发生变化时,ArkUI 会自动触发该组件的 build 方法重新执行,从而实现 UI 的更新。

1.1 基本语法

typescript 复制代码
@Entry
@Component
struct Counter {
  @State count: number = 0;

  build() {
    Column({ space: 20 }) {
      Text(`当前计数:${this.count}`)
        .fontSize(24)
      Button('增加')
        .onClick(() => {
          this.count++;  // 修改 @State 变量,UI 自动刷新
        })
    }
    .width('100%')
    .padding(20)
  }
}
  • @State 只能装饰组件内部定义的变量,不能用于从外部传入的属性。
  • 变量的变化必须是可观察的 (如赋值、数组/对象整体替换等),简单的属性修改(如 this.obj.prop = 'new')需要配合 @Observed / @ObjectLink 才能触发更新(V2 版本中更完善,本文只讨论 V1 基础场景)。

1.2 @State 的特点

特性 说明
所属范围 组件私有状态
触发更新 变量重新赋值时,触发所在组件 build 重新执行
数据流向 组件内部自管理,外部不可直接修改
适用场景 本地开关、计数器、表单输入等独立状态

1.3 注意事项

  • @State 变量必须在声明时或在构造函数中初始化。
  • 对于复杂类型(对象、数组),若需深度观察属性变化,建议使用 @Observed@ObjectLink(V2 特性),或通过整体替换对象/数组来触发刷新。
typescript 复制代码
@State user: { name: string, age: number } = { name: 'Alice', age: 25 };

// 错误:直接修改属性不会触发更新
this.user.age = 26;

// 正确:整体替换
this.user = { ...this.user, age: 26 };

二、@Prop ------ 父到子单向传递

@Prop 用于接收父组件传递过来的数据,并在子组件内部作为一份只读副本 使用。父组件修改数据时,会同步更新子组件;但子组件不能修改 @Prop 的值(修改不会回传给父组件,且子组件自身视图可能更新但不影响父组件状态)。

2.1 基本语法

父组件:

typescript 复制代码
@Entry
@Component
struct Parent {
  @State parentMessage: string = '来自父组件的消息';

  build() {
    Column({ space: 20 }) {
      Text(`父组件:${this.parentMessage}`)
      Button('修改消息').onClick(() => {
        this.parentMessage = '已更新的消息';
      })
      // 向子组件传递 @State 变量
      Child({ childMsg: this.parentMessage })
    }
    .width('100%')
    .padding(20)
  }
}

子组件:

typescript 复制代码
@Component
struct Child {
  @Prop childMsg: string;  // 接收父组件传入的值

  build() {
    Text(`子组件收到:${this.childMsg}`)
      .fontColor(Color.Gray)
  }
}

运行后,点击父组件的按钮,parentMessage 变化,子组件内的 childMsg 自动同步刷新。

2.2 @Prop 的特点

特性 说明
数据来源 父组件传入
修改行为 子组件可修改本地副本,但不会反向影响父组件
同步方式 父组件源数据变化时,子组件副本自动同步
适用场景 展示型子组件,仅需要读取数据,无需回传

2.3 注意事项

  • @Prop 变量不能 在子组件内部通过 this.prop = newValue 去期望父组件更新。
  • @Prop 支持的类型与 @State 类似,包括基础类型和复杂类型。
  • 如果想实现子组件修改数据并同步回父组件,应使用 @Link

@Link@Prop 的基础上实现了双向绑定 。子组件通过 @Link 获取父组件的状态引用,子组件对变量的任何修改都会直接反映到父组件,反之亦然。

3.1 基本语法

父组件:

typescript 复制代码
@Entry
@Component
struct Parent {
  @State isOn: boolean = false;

  build() {
    Column({ space: 20 }) {
      Text(`父组件开关状态:${this.isOn}`)
      // 传递时使用 $ 符号创建引用
      ToggleSwitch({ switchState: $isOn })
    }
    .width('100%')
    .padding(20)
  }
}

子组件(开关):

typescript 复制代码
@Component
struct ToggleSwitch {
  @Link switchState: boolean;  // 双向绑定引用

  build() {
    Row() {
      Text('开关:')
      Toggle({ type: ToggleType.Switch, isOn: this.switchState })
        .onChange((isOn: boolean) => {
          this.switchState = isOn;  // 修改会直接更新父组件
        })
    }
  }
}

注意父组件传值时使用了 $isOn 语法,表示传递的是引用而非值的副本。

特性 说明
数据来源 父组件 @State(或 @Link)变量的引用
修改行为 子组件修改立即同步到父组件,父组件修改也同步到子组件
适用场景 表单控件、开关、输入框等需要父子数据一致的组件

3.3 注意事项

  • @Link 装饰的变量必须从父组件通过 $ 传递引用,不能直接赋值字面量。
  • 如果数据需要在多个层级间传递,可以逐层使用 @Link,但嵌套过深时建议考虑 AppStorage 或状态管理库。
  • @Link 变量不能设置默认值,其初始值完全由父组件提供。
装饰器 数据归属 数据流向 修改影响范围 典型场景
@State 组件自身 内部循环 自身 UI 刷新 本地状态(计数器等)
@Prop 子组件(副本) 父 → 子(单向) 仅子组件本地副本 展示型子组件(标签等)
@Link 父组件(引用) 父 ↔ 子(双向) 父子组件同步变化 输入控件、开关、滑块等

选型建议:

  • 如果状态仅当前组件使用,用 @State
  • 子组件只需展示数据,不需要修改,用 @Prop
  • 子组件需要修改并同步回父组件,用 @Link
  • 深层嵌套或跨页面共享时,使用 AppStorage 或更高级的状态管理方案。

五、AppStorage ------ 全局状态存储

当应用需要在不同页面、不同组件之间 共享状态时,@State / @Prop / @Link 的组件树传递方式会变得繁琐。AppStorage 提供了一种全局的键值对存储机制,任何组件都可以读写其中的数据,且数据的变更会自动通知所有绑定者刷新 UI。

5.1 基本 API

  • AppStorage.setOrCreate<T>(key: string, value: T):创建或更新一个全局状态值。
  • AppStorage.link<T>(key: string):返回双向绑定的引用(类似 @Link),用于组件中修改会同步回 AppStorage。
  • AppStorage.prop<T>(key: string):返回单向同步的值(类似 @Prop),用于只读展示。

5.2 存储与使用

在某处(如入口 Ability 或页面)设置初始值:

typescript 复制代码
import AppStorage from '@ohos.app.ability.AppStorage';

// 在页面初始化时设置
onPageShow() {
  AppStorage.setOrCreate('username', 'Alice');
}

在任意组件中双向绑定:

typescript 复制代码
@Component
struct UserInfo {
  // 通过 @StorageLink 装饰器直接绑定 AppStorage 中的值(双向)
  @StorageLink('username') username: string = '';

  build() {
    Column() {
      Text(`当前用户:${this.username}`)
      TextInput({ placeholder: '修改用户名', text: this.username })
        .onChange((value: string) => {
          this.username = value;  // 修改会直接更新 AppStorage,并同步到所有绑定该 key 的组件
        })
    }
  }
}

注意:@StorageLink 需要配合 @Entry 或自定义组件使用,且 key 需与 AppStorage 中的一致。

单向读取(只展示):

typescript 复制代码
@Component
struct Greeting {
  @StorageProp('username') username: string = 'Guest';

  build() {
    Text(`你好,${this.username}`)
  }
}
  • @StorageProp:单向同步,不能通过组件修改 AppStorage 中的值。
  • @StorageLink:双向同步,修改组件变量会回写 AppStorage。

5.3 应用场景

  • 用户登录信息(用户名、token 等)
  • 全局配置(主题色、字体大小)
  • 跨页面数据共享(购物车数量等)

注意:不要滥用 AppStorage 存储大量复杂状态,避免造成数据流难以追踪。建议只用于真正的全局共享状态。

六、综合示例 ------ 计数器与用户信息联动

下面通过一个完整的示例,综合运用 @State@Prop@LinkAppStorage

场景 :页面顶部显示全局用户名(可从设置页修改),中间有一个计数器(本地状态),底部有一个开关控制是否显示详细信息,开关状态通过 @Link 在父子组件间双向同步。

typescript 复制代码
import AppStorage from '@ohos.app.ability.AppStorage';

// 假设在应用启动时已设置 username
// AppStorage.setOrCreate('username', 'Alice');

@Entry
@Component
struct MainPage {
  @State count: number = 0;
  @State showDetail: boolean = true;

  build() {
    Column({ space: 20 }) {
      // 全局用户名展示(单向)
      UserName()
      Divider()

      // 计数器(本地状态)
      Text(`计数:${this.count}`).fontSize(24)
      Row({ space: 10 }) {
        Button('+').onClick(() => { this.count++ })
        Button('-').onClick(() => { this.count-- })
      }

      // 详情开关(通过 @Link 双向绑定)
      DetailSwitch({ isOn: $showDetail })

      // 根据 showDetail 显示详细信息
      if (this.showDetail) {
        Text('详细信息:当前计数器的值用于演示状态管理。')
          .fontColor(Color.Gray)
      }
    }
    .width('100%')
    .padding(20)
  }
}

@Component
struct UserName {
  @StorageProp('username') username: string = '';

  build() {
    Text(`当前用户:${this.username}`)
      .fontSize(18)
      .fontWeight(FontWeight.Bold)
  }
}

@Component
struct DetailSwitch {
  @Link isOn: boolean;

  build() {
    Row({ space: 10 }) {
      Text('显示详情')
      Toggle({ type: ToggleType.Switch, isOn: this.isOn })
        .onChange((value: boolean) => {
          this.isOn = value;
        })
    }
  }
}

点击 +/- 只影响当前组件;切换开关会同步更新父组件;用户名从 AppStorage 读取,在其他页面修改 AppStorage 后此处会自动刷新。

七、常见错误与注意事项

7.1 直接修改 @State 对象的属性不触发更新

typescript 复制代码
@State user: { name: string } = { name: 'Alice' };
this.user.name = 'Bob';  // ❌ UI 可能不刷新

解决方案 :整体替换对象 this.user = { ...this.user, name: 'Bob' }; 或使用 V2 状态管理。

7.2 @Prop 变量被当成双向绑定使用

typescript 复制代码
// 错误预期:子组件修改 prop 能回传给父组件
@Prop value: string;
this.value = 'new';  // 仅本地副本变化,不影响父组件

请改用 @Link

typescript 复制代码
// 错误
Child({ linkValue: this.parentValue })  // 被当作普通值传递,子组件 @Link 初始化失败
// 正确
Child({ linkValue: $parentValue })

7.4 AppStorage 未初始化就访问

在组件中绑定 @StorageLink@StorageProp 前,确保调用过 AppStorage.setOrCreate 设置初始值,否则变量为 undefined 可能导致异常。

7.5 滥用 AppStorage

不要将 AppStorage 当作所有状态的垃圾桶。仅用于真正的全局共享状态,本地状态仍使用 @State 保持组件的独立性。

八、小结

概念 关键点
@State 组件私有状态,赋值触发 UI 刷新
@Prop 父传子单向数据流,子组件只读副本
@Link 父传子双向数据流,通过 $ 传递引用
AppStorage 全局状态存储,跨页面/组件共享
@StorageProp 从 AppStorage 单向读取
@StorageLink 从 AppStorage 双向绑定

掌握这些状态管理基础后,你可以轻松处理大多数组件间数据通信的场景,为后续学习更复杂的页面逻辑和项目开发奠定坚实基础。


觉得文章有帮助?别忘了:

👍 点赞 👍 -- 给我一点鼓励

⭐ 收藏 ⭐ -- 方便以后查看

🔔 关注 🔔 -- 获取更新通知


标签: #HarmonyOS #ArkUI #状态管理 #@State #@Prop #@Link #AppStorage #学习笔记 #鸿蒙开发

相关推荐
李二。1 小时前
鸿蒙 PC 端文件搜索工具开发实战:从零构建桌面级搜索引擎
搜索引擎·华为·harmonyos
坚果派·白晓明2 小时前
[鸿蒙PC三方库移植适配] 使用 AtomCode + Skills 自动完成libhv鸿蒙化适配
c++·华为·ai编程·harmonyos·atomcode
hanlin032 小时前
基于OpenHarmony 5.0的CAN驱动移植步骤
linux·c语言·华为·can·openharmony·t527
大明者省4 小时前
NOLO HOME和华为 PCVR 助手软件
华为
事界见闻13 小时前
鸿蒙6闪控球功能评测:盯盘、抢单、搜题,一点即达
华为·harmonyos
李二。14 小时前
ArkTS原生 | 知识问答引擎 —— 鸿蒙Next声明式UI实战
ui·华为·harmonyos
坚果的博客15 小时前
【鸿蒙 PC三方库构建系统】【测试验证】HPKCHECK文件详解
华为·harmonyos
世人万千丶15 小时前
鸿蒙PC问题解决:窗口拖动与拉伸时页面布局瞬间错乱、回弹后恢复
学习·华为·开源·harmonyos·鸿蒙·鸿蒙系统
Dream-Y.ocean15 小时前
Windows 鸿蒙 PC 应用开发:Electron 桌面级电子书阅读器开发实战指南
华为·harmonyos