状态管理
ArkUI开发框架提供了多维度的状态管理机制,和UI相关联的数据,不仅可以在组件内使用,还可以在不同组件层级间传递,比如父子组件之间,爷孙组件之间等,也可以是全局范围内的传递,还可以是跨设备传递。另外,从数据的传递形式来看,可以分为只读的单向传递和可变更的双向传递。如下图所示,开发框架提供了多种应用程序状态管理的能力。
@State修饰符
@State
装饰的变量是组件内部的状态数据,当这些状态数据被修改时,将会调用所在组件的 build()
方法刷新UI。 @State
状态数据具有以下特征:
-
支持多种数据类型:允许
class
、number
、boolean
、string
强类型的按值和按引用类型。允许这些强类型构成的数组,即Array<class>
、Array<string>
、Array<boolean>
、Array<number>
。不允许object
和any
。 -
内部私有:标记为
@State
的属性是私有变量,只能在组件内访问。 -
支持多个实例:组件不同实例的内部状态数据独立。
-
需要本地初始化:必须为所有
@State
变量分配初始值,将变量保持未初始化可能导致框架行为未定义,初始值需要是有意义的值,比如设置class
类型的值为null
就是无意义的,会导致编译报错。 -
创建自定义组件时支持通过状态变量名设置初始值:在创建组件实例时,可以通过变量名显式指定
@State
状态属性的初始值。
鸿蒙OS开发 | 更多内容↓点击 | HarmonyOS与OpenHarmony技术 |
---|---|---|
鸿蒙技术文档 | 开发知识更新库gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md在这 |
简单样例如下所示:
@Entry @Component struct ComponentTest {
@State date: string = "时间:" + new Date().getTime(); // data变化会触发build方法执行
build() {
Column({space: 10}) {
Text(`父组件【${this.date}】`) // 显示时间
.fontSize(20)
.backgroundColor(Color.Pink)
Item() // 子组件
Item() // 子组件
Button('更新时间')
.onClick(() => {
this.date = "时间:" + new Date().getTime(); // 点击按钮,date变化,会触发build方法执行
})
}
.width('100%')
.height('100%')
.padding(10)
}
}
// 自定义子组件
@Component struct Item {
@State time: string = "时间:" + new Date().getTime();
build() {
Text(`子组件【${this.time}】`)
.fontSize(20)
.backgroundColor(Color.Grey)
.onClick(() => {
this.time = "时间:" + new Date().getTime(); // 点击更新时间,执行build方法
})
}
}
样例运行结果如下图所示:
@Prop修饰符
开发应用知识已更新gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md参考前往。
@Prop
与 @State
有相同的语义,但初始化方式不同, @Prop
装饰的变量可以和父组件的 @State
变量建立单向的数据绑定。即 @Prop
修饰的变量必须使用其父组件提供的 @State
变量进行初始化,允许组件内部修改 @Prop
变量值但更改不会通知给父组件。 @Prop
状态数据具有以下特征:
-
支持简单数据类型:仅支持
number
、string
、boolean
简单类型; -
内部私有:标记为
@Prop
的属性是私有变量,只能在组件内访问。 -
支持多个实例:组件不同实例的内部状态数据独立。
-
不支持内部初始化:在创建组件的新实例时,必须将值传递给
@Prop
修饰的变量进行初始化,不支持在组件内部进行初始化。简单样例如下所示:
@Entry @Component struct ComponentTest { @State date: string = "时间:" + new Date().getTime(); build() { Column({space: 10}) { Text(`父组件【${this.date}】`) .fontSize(20) .backgroundColor(Color.Pink) Item({time: this.date}) // 必须初始化子组件的time字段 Item({time: this.date}) // 必须初始化子组件的time字段 Button('更新时间') .onClick(() => { this.date = "时间:" + new Date().getTime();// 父组件的更改影响子组件 }) } .width('100%') .height('100%') .padding(10) } } @Component struct Item { @Prop time: string; // 不允许本地初始化 build() { Text(`子组件【${this.time}】`) .fontSize(20) .backgroundColor(Color.Grey) .onClick(() => { this.time = "时间:" + new Date().getTime(); // 子组件的更改不影响父组件 }) } }
样例运行结果如下图所示:
@Link修饰符
@Link
与 @State
有相同的语义,但初始化方式不同, @Link
装饰的变量可以和父组件的 @State
变量建立双向的数据绑定。即 @Link
修饰的变量必须使用其父组件提供的 @State
变量进行初始化,允许组件内部修改 @Link
变量值且更改会通知给父组件。 @Link
状态数据具有以下特征:
- 支持多种数据类型:
@Link
变量的值与@State
变量的类型相同,即class
、number
、string
、boolean
或这些类型的数组。 - 内部私有:标记为
@Link
的属性是私有变量,只能在组件内访问。 - 支持多个实例:组件不同实例的内部状态数据独立。
- 不支持内部初始化:在创建组件的新实例时,必须将值传递给
@Link
修饰的变量进行初始化,不支持在组件内部进行初始化。初始化使用$
符号,例如:$propertiesName。
样例如下:
@Entry @Component struct ComponentTest {
@State date: string = "时间:" + new Date().getTime(); // 定义@State变量
build() {
Column({space: 10}) {
Text(`父组件【${this.date}】`)
.fontSize(20)
.backgroundColor(Color.Pink)
Item({time: $date}) // 初始化子组件time属性使用$符号
Item({time: $date}) // 初始化子组件time属性使用$符号
Button('更新时间')
.onClick(() => {
this.date = "时间:" + new Date().getTime(); // 变更date,子组件的对应属性也变化
})
}
.width('100%')
.height('100%')
.padding(10)
}
}
@Component struct Item {
@Link time: string;
build() {
Text(`子组件【${this.time}】`)
.fontSize(20)
.backgroundColor(Color.Grey)
.onClick(() => {
this.time = "时间:" + new Date().getTime(); // 变更time,父组件的对应属性也变化
})
}
}
样例运行结果如下图所示:
@StorageLink修饰符
@StorageLink(key)
装饰的变量是组件内部的状态数据,当这些状态数据被修改时,将会调用所在组件的 build()
方法进行UI刷新。组件通过使用 @StorageLink(key)
装饰的状态变量与 AppStorage
建立双向数据绑定。当创建包含 @StorageLink
的状态变量的组件时,该状态变量的值将使用 AppStorage
中的值进行初始化,在UI组件中对 @StorageLink
的状态变量所做的更改将同步到 AppStorage
,并从 AppStorage
同步到任何其他绑定实例中,如 PersistentStorage
或其他绑定的UI组件。 @StorageLink
状态数据具有以下特征:
-
支持多种数据类型:支持的数据类型和
@State
一致且支持object
。 -
需要本地初始化:必须为所有
@StorageLink
变量分配初始值。 -
数据状态全局化:使用
@StorageLink
修饰的数据变化后全局都会改变。 -
数据持久化:通过搭配
PersistentStorage
接口实现数据持久化。-
绑定数据
简单样例如下所示:
@Entry @Component struct ComponentTest { @StorageLink('time') time: string = "1648643734154";// 使用StorageLink标记并初始化 build() { Column({space: 10}) { Text(`父组件【${this.time}】`) // 使用time值 .fontSize(20) .backgroundColor(Color.Pink) Button('更新时间') .onClick(() => { this.time = new Date().getTime().toString();// 更改time的值 }) } .width('100%') .height('100%') .padding(10) } }
-
运行结果如下图所示:
- **双向绑定数据**
简单样例如下所示:
```
@Entry @Component struct ComponentTest {
@StorageLink('time') time1: string = "1648643734154";
@StorageLink('time') time2: string = "abcdefefwefwewee";
build() {
Column({space: 10}) {
Text(`父组件【${this.time1}】`)
.fontSize(20)
.backgroundColor(Color.Pink)
Item();
Item();
Button('更新时间')
.onClick(() => {
this.time2 = new Date().getTime().toString();
})
}
.width('100%')
.height('100%')
.padding(10)
}
}
@Component struct Item {
@StorageLink('time') time: string = "OpenHarmony";
build() {
Text(`子组件【${this.time}】`)
.fontSize(20)
.backgroundColor(Color.Grey)
.onClick(() => {
this.time = new Date().getTime().toString();
})
}
}
```
运行结果如下图所示:
- 页面间数据绑定
简单样例如下图所示:
```
// 第一个页面
@Entry @Component struct ComponentTest {
@StorageLink('time') time1: string = "1648643734154";// 应用key的值以首次初始化的值为准
@StorageLink('time') time2: string = "abcdefefwefwewee";// time2以time1的值为准
build() {
Column({space: 10}) {
Text(`父组件【${this.time1}】`)
.fontSize(20)
.backgroundColor(Color.Pink)
Item();// 使用自定义组件
Item();// 使用自定义组件
Button('更新时间')
.onClick(() => {
this.time2 = new Date().getTime().toString();// 更改time2的值,所有使用key的页面都会刷新
})
Button('跨页面数据绑定')
.onClick(() => {
router.push({uri: "pages/test/setting"})// 打开第二个页面
})
}
.width('100%')
.height('100%')
.padding(10)
}
}
// 自定义个组件
@Component struct Item {
@StorageLink('time') time: string = "OpenHarmony";// time的值以key第一次出现的初始化为准
build() {
Text(`子组件【${this.time}】`)
.fontSize(20)
.backgroundColor(Color.Grey)
.onClick(() => {
this.time = new Date().getTime().toString();// 更改time的值,所有使用key的页面都会刷新
})
}
}
// 第二个页面
@Entry @Component struct Setting {
@StorageLink('time') tips: string = "我是第二个页面"; // tips的值以'key'第一次出现的为准
build() {
Column({space: 10}) {
Text(this.tips) // tips的值以'key'第一次出现的为准
.fontSize(20)
.margin(20)
.onClick(() => {
this.tips = "0000000000000" // 更改tips的值,所有使用key的页面都会更新
})
Button('返回')
.onClick(() => {
router.back()// 点击返回,首页的数据会更改
})
}
.width('100%')
.height('100%')
}
}
```
运行结果如下图所示:
- 持久化数据
@StorageLink
搭配 PersistentStorage
接口可以实现数据本地持久化,简单样例如下图所示:
```
// 持久化存储key并设置默认值
PersistentStorage.PersistProp("time", "Hello, OpenHarmony")
@Entry @Component struct ComponentTest {
// 初始化time1,如果AppStorage
@StorageLink('time') time1: string = "1648643734154";
@StorageLink('time') time2: string = "OpenHarmony";
build() {
Column({space: 10}) {
Text(`父组件【${this.time1}】`)
.fontSize(20)
.backgroundColor(Color.Pink)
Item();
Item();
Button('更新时间')
.onClick(() => {
this.time2 = new Date().getTime().toString();
})
Button('跨页面数据绑定')
.onClick(() => {
router.push({uri: "pages/test/setting"})
})
}
.width('100%')
.height('100%')
.padding(10)
}
}
// 自定义组件
@Component struct Item {
@StorageLink('time') time: string = "OpenHarmony";
build() {
Text(`子组件【${this.time}】`)
.fontSize(20)
.backgroundColor(Color.Grey)
.onClick(() => {
this.time = new Date().getTime().toString();
})
}
}
```
运行结果如下图所示:
@Watch修饰符
@Watch
用来监听状态变量的变化,当它修饰的状态变量发生变更时,回调相应的方式,语法结构为:
@State @Watch("function_name") count : number = 0;
上述语句表示:给状态变量 count
增加一个 @Watch
装饰器,通过 @Watch
注册一个回调方法 function_name
, 当状态变量 count
被改变时, 触发 function_name
回调。
简单样例如下所示:
@Entry @Component struct WatchTest {
@State @Watch("onBasketUpdated") shopBasket: Array<number> = [7, 12, 47, 3];
@State totalPurchase: number = 0;
updateTotal(): number {
let sum = 0;
this.shopBasket.forEach((i) => {
sum += i;
});
// 计算新的购物篮总价值,如果超过100RMB,则适用折扣
this.totalPurchase = (sum < 100) ? sum : 0.9 * sum;
return this.totalPurchase;
}
onBasketUpdated(propName: string): void {
this.updateTotal();
}
build() {
Column({space: 10}) {
Text(`${this.totalPurchase}`)
.fontSize(30)
Button("add to basket")
.onClick(() => {
this.shopBasket.push(Math.round(100 * Math.random()))
})
}
.width("100%")
.height("100%")
.padding(10)
}
}
样例运行结果如下图所示:
集合 shopBasket
是一个状态变量,它被 @Watch
修饰符修饰并绑定了 onBasketUpdated()
方法回调,当点击按钮往 shopBasket
里添加数据时会触发 onBasketUpdated()
方法的调用,该方法里边执行了 totalPurchase
的数据计算,最后页面刷新。
@Watch
装饰器只能监听 @State
、 @Prop
、 @Link
、 @ObjectLink
、 @Provide
、 @Consume
、 @StorageProp
以及 @StorageLink
装饰的变量。
小结
通过对ArkUI三种状态管理的介绍,可以根据具体的业务场景选择不同的状态管理模式。
鸿蒙开发岗位需要掌握那些核心要领?
目前还有很多小伙伴不知道要学习哪些鸿蒙技术?不知道重点掌握哪些?为了避免学习时频繁踩坑,最终浪费大量时间的。
自己学习时必须要有一份实用的鸿蒙(Harmony NEXT)资料非常有必要。 这里我推荐,根据鸿蒙开发官网梳理与华为内部人员的分享总结出的开发文档。内容包含了:【ArkTS、ArkUI、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、Harmony南向开发、鸿蒙项目实战】等技术知识点。
废话就不多说了,接下来好好看下这份资料。
如果你是一名Android、Java、前端等等开发人员,想要转入鸿蒙方向发展。可以直接领取这份资料辅助你的学习。鸿蒙OpenHarmony知识←前往。下面是鸿蒙开发的学习路线图。
针对鸿蒙成长路线打造的鸿蒙学习文档。鸿蒙(OpenHarmony )学习手册(共计1236页)与鸿蒙(OpenHarmony )开发入门教学视频,帮助大家在技术的道路上更进一步。
其中内容包含:
《鸿蒙开发基础》鸿蒙OpenHarmony知识←前往
- ArkTS语言
- 安装DevEco Studio
- 运用你的第一个ArkTS应用
- ArkUI声明式UI开发
- .……
《鸿蒙开发进阶》鸿蒙OpenHarmony知识←前往
- Stage模型入门
- 网络管理
- 数据管理
- 电话服务
- 分布式应用开发
- 通知与窗口管理
- 多媒体技术
- 安全技能
- 任务管理
- WebGL
- 国际化开发
- 应用测试
- DFX面向未来设计
- 鸿蒙系统移植和裁剪定制
- ……
《鸿蒙开发实战》鸿蒙OpenHarmony知识←前往
- ArkTS实践
- UIAbility应用
- 网络案例
- ……
最后
鸿蒙是完全具备无与伦比的机遇和潜力的;预计到年底将有 5,000 款的应用完成原生鸿蒙开发,这么多的应用需要开发,也就意味着需要有更多的鸿蒙人才。鸿蒙开发工程师也将会迎来爆发式的增长,学习鸿蒙势在必行!