状态管理
ArkUI 开发框架提供了多维度的状态管理机制,和 UI 相关联的数据,不仅可以在组件内使用,还可以在不同组件层级间传递,比如父子组件之间,爷孙组件之间等,也可以是全局范围内的传递,还可以是跨设备传递。另外,从数据的传递形式来看,可以分为只读的单向传递和可变更的双向传递。如下图所示,开发框架提供了多种应用程序状态管理的能力。
组件级别
@State:@State 装饰的变量拥有其所属组件的状态,可以作为其子组件单向和双向同步的数据源。当其数值改变时,会引起相关组件的渲染刷新。
@Prop:@Prop 装饰的变量可以和父组件建立单向同步关系,@Prop 装饰的变量是可变的,但修改不会同步回父组件。
@Link:@Link 装饰的变量和父组件构建双向同步关系的状态变量,父组件会接受来自@Link 装饰的变量的修改的同步,父组件的更新也会同步给@Link 装饰的变量。
@Provide/@Consume:@Provide/@Consume 装饰的变量用于跨组件层级(多层组件)同步状态变量,可以不需要通过参数命名机制传递,通过 alias(别名)或者属性名绑定。
@Observed:@Observed 装饰 class,需要观察多层嵌套场景的 class 需要被@Observed 装饰。单独使用@Observed 没有任何作用,需要和@ObjectLink、@Prop 连用。
@ObjectLink:@ObjectLink 装饰的变量接收@Observed 装饰的 class 的实例,应用于观察多层嵌套场景,和父组件的数据源构建双向同步
管理应用拥有的状态(Application)
LocalStorage:页面级 UI 状态存储,通常用于 UIAbility 内、页面间的状态共享。
AppStorage:特殊的单例 LocalStorage 对象,由 UI 框架在应用程序启动时创建,为应用程序 UI 状态属性提供中央存储。
PersistentStorage:持久化存储 UI 状态,通常和 AppStorage 配合使用,选择 AppStorage 存储的数据写入磁盘,以确保这些属性在应用程序重新启动时的值与应用程序关闭时的值相同。
Environment:应用程序运行的设备的环境参数,环境参数会同步到 AppStorage 中,可以和 AppStorage 搭配使用。
初级组件状态管理入门
为了⽅便开发者管理组件状态,ArkTS 提供了⼀系列状态相关的装饰器,例如 @State , @Prop , @Link , @Provide 和 @Consume 等等
@State
@State
⽤于装饰当前组件的状态变量, @State
装饰的变量发⽣变化时会驱动当前组件的视图刷新。
INFO
@State 装饰的变量必须进行本地初始化
具体语法如下:
@State count:number = 1;
- 代码示例:
@Entry
@Component
struct LightPage{
// 定义状态
@State countNumber:number = 0 ;
build(){
Column({space:20}){
Text(String(this.countNumber)).textAlign(TextAlign.Center).fontSize(32).fontWeight(FontWeight.Bold)
Button("增加").type(ButtonType.Capsule).onClick(()=>{
this.countNumber++;
})
}.width('100%').height('100%').justifyContent(FlexAlign.Center)
}
}
@Prop
- 简单来说 子组件改变父亲的值 父亲不会变化
@Prop
也可⽤于装饰状态变量, @Prop
装饰的变量发⽣变化时也会驱动当前组件的视图刷新,除此之外, @Prop
装饰的变量还可以同步⽗组件中的状态变量,但只能单向同步,也就是⽗组件的状态变化会⾃动同步到⼦组件,⽽⼦组件的变化不会同步到⽗组件。
INFO
注意: @Prop 装饰的变量不允许本地初始化,只能通过⽗组件向⼦组件传参进⾏初始化。
- 代码如下
子组件
@Component
struct SonButton {
@Prop countNumber:number
build() {
Column({space:20}){
Text(String(this.countNumber)).textAlign(TextAlign.Center).fontSize(32).fontWeight(FontWeight.Bold)
Button("这是子组件的按钮增加").type(ButtonType.Capsule).backgroundColor(Color.Green).onClick(()=>{
this.countNumber++;
})
}
}
}
父组件如下:
@Entry
@Component
struct LightPage{
// 定义状态
@State countNumber:number = 0 ;
build(){
Column({space:20}){
Text(String(this.countNumber)).textAlign(TextAlign.Center).fontSize(32).fontWeight(FontWeight.Bold)
Button("这是父组件的按钮增加").type(ButtonType.Capsule).onClick(()=>{
this.countNumber++;
})
SonButton({countNumber:this.countNumber});
}.width('100%').height('100%').justifyContent(FlexAlign.Center)
}
}
@Link
- 简单来说子组件改变父元素的值,父元素的值变化
@Link
也可⽤于装饰状态变量, @Link
装饰的变量发⽣变化时也会驱动当前组件的视图刷新,除此之外, @Link
变量同样会同步⽗组件状态,并且能够双向同步。也就是⽗组件的变化会同步到⼦组件,⼦组件的变化也会同步到⽗组件。
INFO
注意: @Link 装饰的变量不允许本地初始化,只能由⽗组件通过传参进⾏初始化,并且⽗组件必须使⽤ $变量名
的⽅式传参,以表示传递的是变量的引⽤。
- 代码如下:
父组件:
INFO
传递的参数必须是$开头,不加 this
@Entry
@Component
struct LightPage{
// 定义状态
@State countNumber:number = 0 ;
build(){
Column({space:20}){
Text(String(this.countNumber)).textAlign(TextAlign.Center).fontSize(32).fontWeight(FontWeight.Bold)
Button("这是父组件的按钮增加").type(ButtonType.Capsule).onClick(()=>{
this.countNumber++;
})
SonButton({countNumber:$countNumber});
}.width('100%').height('100%').justifyContent(FlexAlign.Center)
}
}
- 子组件
@Component
struct SonButton {
@Link countNumber:number
build() {
Column({space:20}){
Text(String(this.countNumber)).textAlign(TextAlign.Center).fontSize(32).fontWeight(FontWeight.Bold)
Button("这是子组件的按钮增加").type(ButtonType.Capsule).backgroundColor(Color.Green).onClick(()=>{
this.countNumber++;
})
}
}
}
@Provide 和@Consume
- 简单来说就是祖先托梦给后代,后代通过@Consume 来接收,后代变了,祖先也变了
INFO
@Provide 和 @Consume ⽤于跨组件层级传递状态信息,其中 @Provide ⽤于装饰祖先组件的状态变量, @Consume ⽤于装饰后代组件的状态变量。可以理解为祖先组件提供(Provide)状态信息供后代组件消费(Consume),并且祖先和后代的状态信息可以实现双向同步。
INFO
@Provide
装饰变量必须进⾏本地初始化,⽽@Consume
装饰的变量不允许进⾏本地初始化。另外, @Provide
和 @Consume
装饰的变量不是通过⽗组件向⼦组件传参的⽅式进⾏绑定的,⽽是通过相同的变量名进⾏绑定的。
- 代码
祖先代码
@Entry
@Component
struct LightPage{
// 定义状态
@Provide countNumber:number = 0 ;
build(){
Column({space:20}){
Text(String(this.countNumber)).textAlign(TextAlign.Center).fontSize(32).fontWeight(FontWeight.Bold)
Button("这是父组件的按钮增加").type(ButtonType.Capsule).onClick(()=>{
this.countNumber++;
})
SonButton();
}.width('100%').height('100%').justifyContent(FlexAlign.Center)
}
}
父亲代码
@Component
struct SonButton {
build() {
Column({space:20}){
SunComponent()
}
}
}
孙子代码
@Component
struct SunComponent {
@Consume countNumber:number
build() {
Column(){
Text(String(this.countNumber)).textAlign(TextAlign.Center).fontSize(32).fontWeight(FontWeight.Bold)
Button("这是孙子组件的按钮增加").type(ButtonType.Capsule).onClick(()=>{
this.countNumber++;
})
}
}
}
终极组件状态管理入门
详解图如下
@State
允许装饰的类型
INFO
@State 允许装饰的变量类型有 string 、 number 、 boolean 、 object 、 class 和 enum 类型,以及这些类型的数组。
框架能够观察到的变化
并不是 @State
状态变量的所有更改都会引起 UI 的刷新,只有可以被框架观察到的修改才会引起 UI 刷新。能被框架观察到的变化如下
- 基础类型
boolean,string,number 类型
INFO
当 @State 装饰的变量类型为 boolean 、 string 、 number 类型时,可以观察到赋值的变化
例如:
//状态变量定义
@State count:number = 1;
//状态变量操作
this.count++; //可以观察到
不能观察第二层变化的
- class Object 类型
INFO
当 @State 装饰的变量类型为 class 或者 object 时,可以观察到变量⾃身赋值的变化,和其属性赋值的变化。需要注意的是,若某个属性也为 class 或者 object,则嵌套属性的变化是观察不到的。
- 代码如下
@Entry
@Component
struct LightPage{
@State employee: Employee = new Employee('张三', 28, new Job('销售', 8000))
build(){
Column({space:20}){
Text(this.employee.name).textAlign(TextAlign.Center).fontSize(32).fontColor(Color.Green)
Text(String(this.employee.age)).textAlign(TextAlign.Center).fontSize(32).fontColor(Color.Green)
Text(String(this.employee.job.salary)).textAlign(TextAlign.Center).fontSize(32).fontColor(Color.Green)
Button("点击改变").type(ButtonType.Capsule).onClick(()=>{
this.employee = new Employee('李四', 35, new Job('销售', 8000))
})
Button("点击改变job下面的salary").type(ButtonType.Capsule).onClick(()=>{
this.employee.job.salary++
})
}.width('100%').height('100%').justifyContent(FlexAlign.Center)
}
}
//类型定义
class Employee {
name: string;
age: number;
job: Job;
constructor(name: string, age: number, job: Job) {
this.name = name;
this.age = age;
this.job = job;
}
}
class Job {
name: string;
salary: number;
constructor(name: string, salary: number) {
this.name = name;
this.salary = salary;
}
}
- 数组类型
INFO
当 @State 装饰的变量类型为数组时,可以观察到数组本身赋值的变化,和其元素的添加、删除及更新的变化,若元素类型为 class 或者 object 时,元素属性的变化,是观察不到的。
代码如下
@Entry
@Component
struct LightPage{
@State employee: Employee[] = [new Employee('张三', 28, new Job('销售', 8000))]
build(){
Column({space:20}){
Text(this.employee[0].name).textAlign(TextAlign.Center).fontSize(32).fontColor(Color.Green)
Text(String(this.employee[0].age)).textAlign(TextAlign.Center).fontSize(32).fontColor(Color.Green)
Text(String(this.employee[0].job.salary)).textAlign(TextAlign.Center).fontSize(32).fontColor(Color.Green)
Button("点击改变").type(ButtonType.Capsule).onClick(()=>{
this.employee[0] = new Employee('李四', 35, new Job('销售', 8000))
})
Button("点击改变job下面的salary").type(ButtonType.Capsule).onClick(()=>{
this.employee[0].job.salary++
})
}.width('100%').height('100%').justifyContent(FlexAlign.Center)
}
}
//类型定义
class Employee {
name: string;
age: number;
job: Job;
constructor(name: string, age: number, job: Job) {
this.name = name;
this.age = age;
this.job = job;
}
}
class Job {
name: string;
salary: number;
constructor(name: string, salary: number) {
this.name = name;
this.salary = salary;
}
}
总结
INFO
对于 class 、 object 和数组类型,框架仅能观察到 @State 变量第⼀层属性的变化,例如 employee.age++ , persons[0]=new Person('张三',22) ,但第⼆层属性的变化是观察不到的,例如 employee.job.salary++ 和 persons[1].age++ 。
@Prop
允许装饰的类型
INFO
@Prop 允许装饰的变量类型有 string 、 number 、 boolean 、 enum ,注意不⽀持 class 、 object 和数组。
能够观察到的变化
当装饰的类型是允许的类型,即 string 、 number 、 boolean 、 enum 类型时,所有赋值的变化都可以观察到。
@Link
允许装饰的类型(同@State)
@Link 允许装饰的变量类型有 string 、 number 、 boolean 、 object 、 class 和 enum 类型,以及这些类型的数组。
框架能够观察到的变化(同@State)
- 当装饰的数据类型为boolean、string、number类型时,可以观察到数值的变化。
- 当装饰的数据类型为class或者object时,可以观察到变量⾃身赋值的变化,和其属性赋值的变化。需要注意的是,若某个属性也为 class 或者 object,则嵌套属性的变化是观察不到的。
- 当装饰的数据类型为数组时,可以可以观察到数组本身赋值的变化,和其元素的添加、删除及更新的变化,若元素类型为 class 或者 object 时,元素属性的变化,是观察不到的。
@Provide 和 @Consume
允许装饰的类型(同 @State)
INFO
@Provide 和 @Consume 允许装饰的变量类型有 string 、 number 、 boolean 、 object 、 class 和 enum 类型,以及这些类型的数组。
能够观察到的变化(同@State)
- 当装饰的数据类型为boolean、string、number类型时,可以观察到数值的变化。
- 当装饰的数据类型为class或者object时,可以观察到变量⾃身赋值的变化,和其属性赋值的变化。需要注意的是,若某个属性也为 class 或者 object,则嵌套属性的变化是观察不到的。
- 当装饰的数据类型为数组时,可以可以观察到数组本身赋值的变化,和其元素的添加、删除及更新的变化,若元素类型为 class 或者 object 时,元素属性的变化,是观察不到的。
@ObjectLink 和 @Observed
前⽂所述的装饰器都仅能观察到状态变量第⼀层的变化,⽽第⼆层的变化是观察不到的。如需观察到这些状态变量第⼆层的变化,则需要⽤到 @ObjectLink 和 @Observed 装饰器。
注意
@Observed 修饰类, @ObjectLink 修饰的只能是@Observed 修饰的类
@ObjectLink 不能再@Entry 装饰的自定义组件中使用
@ObjectLink 不能赋值
流程
注意
创建@Observe 类(记得加个 change 属性)
实例化
发送过去
接收
改变
基础版使用
- 父元素
import Header from '../views/Header';
// 1 创建@Observe类
@Observed
export class AllData {
name: string = ''
age: number = 0
change: boolean = false
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
@Entry
@Component
struct Index3 {
// 2 实例化
@State AllDataResult: AllData = new AllData("张三", 56)
build() {
Column() {
Row() {
Text(this.AllDataResult.name).fontColor(Color.Green).fontSize(36)
}
Row() {
Text(this.AllDataResult.age.toString()).fontColor(Color.Green).fontSize(36)
}
Divider().height(50).color(Color.Red)
// 3 传递过去
Header({ allData: this.AllDataResult })
}
}
}
- Header 组件
import { AllData } from '../pages/Index3'
@Extend(Text)
function commonStyle() {
.fontColor(Color.Red)
.fontSize(36)
}
@Component
export default struct Header {
// 4 接收
@ObjectLink allData: AllData
build() {
Column() {
Row() {
Text(this.allData.name).commonStyle().onClick(() => {
this.allData.name = "改变了哟"
})
}
Row() {
Text(this.allData.age.toString()).commonStyle().onClick(() => {
this.allData.age = 68
})
}
}
}
}
进阶版
注意
只改变子类,页面上不会有变化
只有把父元素的一个属性改了.子类才会变化
类里面嵌套类
- 父元素
import Header from '../views/Header';
// 1 创建@Observe类
@Observed
export class AllData {
name: string = ''
age: number = 0
change: boolean = true
children: Child = new Child("李四", 25)
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
class Child {
name: string = ""
age: number = 0
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}
@Entry
@Component
struct Index3 {
// 2 实例化
@State AllDataResult: AllData = new AllData("张三", 56)
build() {
Column() {
Row() {
Text(this.AllDataResult.children.name).fontColor(Color.Green).fontSize(36)
}
Row() {
Text(this.AllDataResult.children.age.toString()).fontColor(Color.Green).fontSize(36)
}
Divider().height(50).color(Color.Red)
// 3 传递过去
Header({ allData: this.AllDataResult })
}
}
}
- 子元素
import { AllData } from '../pages/Index3'
@Extend(Text)
function commonStyle() {
.fontColor(Color.Red)
.fontSize(36)
}
@Component
export default struct Header {
// 4 接收
@ObjectLink allData: AllData
build() {
Column() {
Row() {
Text(this.allData.children.name).commonStyle().onClick(() => {
this.allData.change = !this.allData.change;
this.allData.children.name = "改变了哟"
})
}
Row() {
Text(this.allData.children.age.toString()).commonStyle().onClick(() => {
this.allData.change = !this.allData.change;
this.allData.children.age = 68
})
}
}
}
}
类里面嵌套数组
- 父组件
import Header from '../views/Header';
interface Item {
name: string
age: number
}
// 1 创建@Observe类
@Observed
export class AllData {
name: string = ''
age: number = 0
change: boolean = true
children: Item[] = [
{
name: "张三",
age: 32
},
{
name: "李四",
age: 26
}
]
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
class Child {
name: string = ""
age: number = 0
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}
@Entry
@Component
struct Index3 {
// 2 实例化
@State AllDataResult: AllData = new AllData("张三", 56)
build() {
Column() {
Row() {
Text(this.AllDataResult.children[0].name).fontColor(Color.Green).fontSize(36)
}
Row() {
Text(this.AllDataResult.children[0].age.toString()).fontColor(Color.Green).fontSize(36)
}
Divider().height(50).color(Color.Red)
// 3 传递过去
Header({ allData: this.AllDataResult })
}
}
}
- 子组件
import { AllData } from '../pages/Index3'
@Extend(Text)
function commonStyle() {
.fontColor(Color.Red)
.fontSize(36)
}
@Component
export default struct Header {
// 4 接收
@ObjectLink allData: AllData
build() {
Column() {
Row() {
Text(this.allData.children[0].name).commonStyle().onClick(() => {
this.allData.change = !this.allData.change;
this.allData.children[0].name = "改变了名字"
})
}
Row() {
Text(this.allData.children[0].age.toString()).commonStyle().onClick(() => {
this.allData.change = !this.allData.change;
this.allData.children[0].age = 68
})
}
}
}
}