AI 摘要

文章系统梳理 HarmonyOS 4.0 ArkTS 状态管理:@Prop 单向、@Link 双向父子同步;@Provide/@Consume 跨代共享;@Watch 监听;@Observed/@ObjectLink 解决嵌套对象同步;LocalStorage 页面级、AppStorage 应用级、PersistentStorage 持久化存储,并附完整代码示例与动图效果,帮助开发者快速掌握各种场景的状态管理技巧。

组件状态共享

父子单向状态共享

参考文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V2/arkts-prop-0000001473537702-V2

@Prop 装饰的变量可以和父组件建立单向的同步关系。

  • @Prop 只能接收 string、number、boolean、enum 数据类型。
  • @Prop 是单向状态共享,父组件的数据发生变化会同步到子组件,子组件的数据发生变化不会同步到父组件。
  • 父组件更的数据新后,会覆盖子组件的 Prop 数据。
  • @Prop 不能在 @Entry 装饰的自定义组件中使用。

示例:父子单向状态共享

Index 组件(src/main/ets/pages/Index.ets):

// 父组件
@Entry
@Component
struct Index {
  // 父组件声明数据
  @State
  count: number = 0

  build() {
    Column({space: 16}){
      Text('父组件的count:' + this.count)

      // 父组件按钮被点击,父子count均发生变化
      Button('parent count++')
        .onClick(() => {
          this.count++
        })

      Divider()

      // 在父组件中调用子组件
      // 父组件的count传递给子组件
      Child({count: this.count})
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }
}

// 子组件
@Component
struct Child {
  // 子组件要接收一个count参数
  @Prop
  count: number

  build() {
    Column({space: 16}){
      Text('子组件接收到的count:' + this.count)

      // 子组件按钮被点击,子count发生变化
      // 父count不变,为单向数据传递
      Button('child count++')
        .onClick(() => {
          this.count++
        })
    }
  }
}

示例效果:


父子双向状态共享

参考文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V2/arkts-link-0000001524297305-V2

子组件中被 @Link 装饰的变量与其父组件中对应的数据源建立双向数据绑定。

  • @Link 支持简单数据类型(string、number 等)和复杂数据类型(Object、class 等)。
  • @Link 是双向状态共享,父组件的数据发生变化会同步到子组件,子组件的数据发生变化也会同步到父组件。
  • @Link 不能在 @Entry 装饰的自定义组件中使用。
  • 父组件传值的时候需要使用 $ 而不是 this,子组件 中使用 @Link 修饰数据。

示例:父子双向状态共享

Index 组件(src/main/ets/pages/Index.ets):

@Entry
@Component
struct Index {
  @State
  count: number = 0

  build() {
    Column({space: 16}){
      Text('父组件的count:' + this.count)

      Button('parent count++')
        .onClick(() => {
          this.count++
        })

      Divider()

      // 在父组件中调用子组件
      // 父组件的count传递给子组件
      // 双向传递不能使用this传递,要使用$传递
      Child({count: $count})
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }
}

@Component
struct Child {
  // 子组件要接收一个 count 参数
  @Link
  count: number

  build() {
    Column({space: 16}){
      Text('子组件接收到的count:' + this.count)

      // 子组件按钮被点击,父子 count 均发生变化
      Button('child count++')
        .onClick(() => {
          this.count++
        })
    }
  }
}

示例效果:


后代组件状态共享

参考文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V2/arkts-provide-and-consume-0000001473857338-V2

@Provide 和 @ Consume 应用于与后代组件的双向数据同步,应用于状态数据在多个层级之间传递的场景。

  • @Provide 装饰的状态变量自动对其所有后代组件可用。
  • 后代通过使用 @Consume 去获取 @Provide 提供的变量,建立在 @Provide 和 @Consume 之间的双向数据同步。
  • @Provide 和 @Consume 可以通过相同的变量名或者相同的变量别名绑定,变量类型必须相同。

示例:后代组件状态共享

Index 组件(src/main/ets/pages/Index.ets):

// 父组件
@Entry
@Component
struct Index {
  // 父组件定义数据
  // 自动对其所有后代组件可用
  @Provide
  count: number = 0

  build() {
    Column({space: 16}){
      Text('父组件的count:' + this.count)

      // 父组件按钮被点击,父、子、后代count均发生变化
      Button('parent count++')
        .onClick(() => {
          this.count++
        })

      Divider()

      // 父组件调用子组件
      Child()
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }
}

// 子组件
@Component
struct Child {
  build() {
    Column({space: 16}){
      Text('子组件')

      Divider()

      // 子组件调用后代组件
      GrandSon()
    }
  }
}

// 后代组件
@Component
struct GrandSon {
  // 后代组件直接接收父组件传递的count
  @Consume
  count: number

  build() {
    Column({space: 16}){
      Text('后代组件接收到的count:' + this.count)

      // 后代组件按钮被点击,父、子、后代 count 均发生变化
      Button('child count++')
        .onClick(() => {
          this.count++
        })
    }
  }
}

示例效果:


状态监听器

参考文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V2/arkts-watch-0000001473697342-V2

@Watch 应用于对状态变量的监听。

  • 在第一次初始化的时候,@Watch 装饰的方法不会被调用。
  • @Watch 方法在自定义组件的属性变更之后同步执行。
  • @Watch 装饰器必须写在 @State / @Prop / @Link 注解的后面。

示例:状态监听器

Index 组件(src/main/ets/pages/Index.ets):

import promptAction from '@ohos.promptAction'

@Entry
@Component
struct Index {
  @State
  @Watch('onCountUpdate') // 监听count变化
  count: number = 0

  // 定义一个监听函数
  onCountUpdate() {
    promptAction.showToast({ message: 'count 变化' })
  }

  build() {
    Column({space: 16}){
      Text('count:' + this.count)

      Button('count++')
        .onClick(() => {
          this.count++
        })
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }
}

示例效果:


解决嵌套引用数据问题

参考文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V2/arkts-observed-and-objectlink-0000001473697338-V2

当 @State 装饰的是一个嵌套的对象类型、对象数组时,@State 无法监听到嵌套的数据的变化。

@ObjectLink 和 @Observed 类装饰器用于在涉及嵌套对象或数组的场景中进行双向数据同步。

  • 被 @Observed 装饰的类,可以被观察到属性的变化。
  • 子组件中 @ObjectLink 装饰器装饰的状态变量用于接收 @Observed 装饰的类的实例,和父组件中对应的状态变量建立双向数据绑定。
  • @ObjectLink 装饰器不能在 @Entry 装饰的自定义组件中使用。

示例:解决嵌套引用数据问题

Index 组件(src/main/ets/pages/Index.ets):

// 使用@Observed装饰User类
@Observed
class User {
  name: string
  age: number

  // 类必须提供构造方法
  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }
}

// 父组件
@Entry
@Component
struct Index {
  // 实例化User对象数组
  @State
  userList: User[] = [
  // 对象数组中的对象要使用new创建
  // 通过初始化构造函数的方式往数组中添加
    new User('张三', 18),
    new User('李四', 19)
  ]

  build() {
    Column({space: 16}){
      Text('父组件区域')

      Divider()

      // 父组件调用子组件1展示信息
      Text('子组件1区域')
      ForEach(this.userList, (item: User, index: number) => {
        Child({user: item})
      })

      Divider()

      // 父组件调用子组件2展示信息
      // 可以观察到this.userList的变化
      Text('子组件2区域')
      ForEach(this.userList, (item: User, index: number) => {
        ItemList({user: item})
      })

      // ForEach遍历的是同一个this.userList
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }
}

// 子组件1
@Component
struct Child {
  // 接收@Observed装饰的类的实例
  // 进行双向数据绑定
  @ObjectLink
  user: User

  build() {
    Row({space: 16}) {
      Text(this.user.name + '的年龄是:' + this.user.age)

      // 子组件1中的数据被修改,父组件的userList中的数据也被修改
      // 故子组件2中的数据也被修改
      Button('age++')
        .onClick(() => {
          this.user.age++
        })
    }
  }
}

// 子组件2
@Component
struct ItemList {
  @ObjectLink
  user: User

  build() {
    Row({space: 16}) {
      Text(this.user.name + '的年龄是:' + this.user.age)
    }
  }
}

示例效果:


应用状态

localStorage

参考文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V2/arkts-localstorage-0000001524537149-V2

LocalStorage 是 ArkTS 为构建页面级别状态变量提供存储的内存内“数据库”。

  • localStorage 是页面级的状态共享。
  • 通过 @Entry 装饰器接收的参数可以在页面内共享同一个 LocalStorage 实例。
  • LocalStorage 也可以在 UIAbility 内,页面间共享状态。

页面内共享 LocalStorage:

  • 使用构造函数创建 LocalStorage 实例。
  • 使用 @Entry 装饰器将 LocalStorage 实例添加到顶层组件中。
  • @LocalStorageProp 绑定 LocalStorage 对给定的属性,建立单向数据同步。
  • @LocalStorageLink 绑定 LocalStorage 对给定的属性,建立双向数据同步。
  • @LocalStorageProp 或 @LocalStorageLink 装饰的数据需要设置默认值。

示例:页面内共享 LocalStorage

Index 组件(src/main/ets/pages/Index.ets):

// 创建一个LocalStorage实例
const localStorage = new LocalStorage({
  count: 10
})

// 将实例对象传给Entry
@Entry(localStorage)
@Component
struct Index {
  // 建立双向数据同步
  @LocalStorageLink('count')
  count: number = 0

  build() {
    Column({space: 16}) {
      Text('我是父组件')
      Text('count is ' + this.count)

      // 按钮点击,父子组件数据均会发生变化
      Button('count++')
        .onClick(() => {
          this.count++
        })

      Divider()

      // 父组件调用子组件
      Child()
    }
    .padding(20)
  }
}

@Component
struct Child {
  // 建立双向数据同步
  @LocalStorageLink('count')
  count: number = 0

  build() {
    Column({space: 16}) {
      Text('我是子组件')
      Text('count is ' + this.count)
    }
  }
}

示例效果:


页面间共享 LocalStorage:

  • 修改 src/main/ets/entryability/EntryAbility.ts,在 UIAbility 创建 LocalStorage,并通过 loadContent 提供给加载的窗口。
  • 在页面使用 LocalStorage.GetShared() 得到实例,通过 @Entry 传入页面。
  • 页面间共享只在模拟器或者实机上有效,不能在 Preview 预览器中使用。

示例:页面间共享 LocalStorage

EntryAbility(src/main/ets/pages/entryability.ts):

export default class EntryAbility extends UIAbility {

  // 实例化LocalStorage
  localStorage = new LocalStorage({
    count: 10
  })

  // ...

  onWindowStageCreate(windowStage: window.WindowStage) {
    // ...

    // 通过windowStage.loadContent提供localStorage给窗口
    windowStage.loadContent('pages/Index', this.localStorage, (err, data) => {
      // ...
    });
  }

  // ...
}

Index 组件(src/main/ets/pages/Index.ets):

// 获取共享的LocalStorage实例
const localStorage = LocalStorage.GetShared()

// 传给Entry
@Entry(localStorage)
@Component
struct Index {
  // 建立双向数据同步
  @LocalStorageLink('count')
  count: number = 0

  build() {
    Column({space: 16}) {
      Text('Home Page')
      Text('LocalStorage count is ' + this.count)
      Button('count++')
        .onClick(() =>{
          this.count++
        })

      Navigator({target: 'pages/Other'}) {
        Button('点我跳转到 Other Page')
      }
    }
    .padding(20)
    .width('100%')
  }
}

OtherPage 组件(src/main/ets/pages/OtherPage.ets):

const storage = LocalStorage.GetShared()
    
@Entry(storage)
@Component
struct OtherPage {
  @LocalStorageLink('count')
  count: number = 0

  build() {
    Column({space: 16}) {
      Text('Other Page')
      // 数据是双向的
      Text('LocalStorage count is ' + this.count)
      Button('count++')
        .onClick(() =>{
          this.count++
        })

      Navigator({target: 'pages/Index'}) {
        Button('点我跳转到Home Page')
      }
    }
    .padding(20)
    .width('100%')
  }
}

示例效果:


appStorage

参考文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V2/arkts-appstorage-0000001524417209-V2

AppStorage 是应用全局的 UI 状态存储,是和应用的进程绑定的,由 UI 框架在应用程序启动时创建,为应用程序 UI 状态属性提供中央存储。

  • AppStorage 是应用级的全局状态共享。
  • AppStorage 是单例,通过其 SetOrCreate 方法初始化数据。
  • @StorageProp 绑定 AppStorage 对给定的属性,建立单向数据同步。
  • @StorageLink 绑定 AppStorage 对给定的属性,建立双向数据同步。
  • @StorageProp 或 @StorageLink 装饰的数据需要设置默认值。

示例:全局共享 appStorage

Index 组件(src/main/ets/pages/Index.ets):

import promptAction from '@ohos.promptAction'

// 初始化数据
AppStorage.SetOrCreate('count', 10)

@Entry
@Component
struct Index {
  // 建立双向数据同步
  @StorageLink('count')
  count: number = 0

  build() {
    Column({space: 16}) {
      Text('AppStorage count is ' + this.count)
      Button('count++')
        .onClick(() =>{
          // count被修改
          this.count++
        })

      Button('Get++')
        .onClick(() =>{
          // 获取数据
          const count = AppStorage.Get('count')
          promptAction.showToast({ message: 'count is ' + count })
        })

      Button('Set++')
        .onClick(() =>{
          // 修改数据
          const count = AppStorage.Set('count', 99)
          promptAction.showToast({ message: 'count set ' + count })
        })

      Divider()

      Child()
    }
    .padding(20)
  }
}

@Component
struct Child {
  // 建立双向数据同步
  @StorageLink('count')
  count: number = 0

  build() {
    Column({space: 16}) {
      Text('Child')
      Text('count is ' + this.count)
    }
  }
}

示例效果:


PersistentStorage

参考文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V2/arkts-persiststorage-0000001474017166-V2

PersistentStorage 可以将选定的 AppStorage 属性保留在设备磁盘上(即持久化)。

  • PersistentStorage 支持 number、string、boolean、enum 等简单类型。
  • UI 和业务逻辑不直接访问 PersistentStorage 中的属性,所有属性访问都是对 AppStorage 的访问,AppStorage 中的更改会自动同步到 PersistentStorage。
  • PersistentStorage 和 AppStorage 关联在一起。
  • 持久化变量最好是小于 2kb 的数据,如果需要存储大量的数据,建议使用数据库 API。

示例:持久化存储状态

Index 组件(src/main/ets/pages/Index.ets):

// 初始化数据
// AppStorage.SetOrCreate('count', 10)
// 运行时,先到硬盘上查找count,若数据不存在,初始化值为10
PersistentStorage.PersistProp('count', 10)

@Entry
@Component
struct Index {
  // 通过AppStorage的方式建立双向数据同步
  @StorageLink('count')
  count: number = 0

  build() {
    Column({space: 16}) {
      Text('PersistentStorage count is ' + this.count)
      Button('count++')
        .onClick(() =>{
          this.count++
        })

      Divider()
    }
    .padding(20)
  }
}