HarmonyOS 4.0 应用开发快速入门之 05-状态管理
AI 摘要
组件状态共享
父子单向状态共享
参考文档: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++
})
}
}
}示例效果:
后代组件状态共享
@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)
}
}示例效果:
解决嵌套引用数据问题
当 @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
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
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
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)
}
}