ArkUI-状态管理
组件状态管理
ArkUI的组件状态管理,提供了@State
、@Prop
、@Link
、@provide
和@Consume
、@Observed
、@ObjectLink
等装饰器。
@State装饰器:组件内状态
在状态变量相关装饰器中,@State是最基础的,使变量拥有状态属性的装饰器,他也是大部分状态变量的数据源。
概述
@State装饰的变量,与声明式范式中的其他被装饰变量一样,是私有的,只能从组件内部访问,在声明时必须指定其类型和本地初始化。初始化也可以选择使用命名参数机制从父组件完成初始化。
@State装饰的变量拥有以下特点:
- @State装饰的变量与子组件中的@Prop装饰变量之间建立单向数据同步,与@Link、@ObjectLink装饰变量之间建立双向数据同步。
- @State装饰的变量的生命周期与其所属的自定义组件的生命周期相同。
观察变化
- 当装饰的数据为string、boolean、number类型时,可以观察到数值的变化。
- 当装饰的数据类型为class或者object时,可以观察到自身的赋值的变化,和其属性的变化,即Object.keys(observedObject)返回的所有属性。当class中嵌套class时,嵌套属性的赋值是观察不到的。
- 当装饰的对象是array时,可以观察到数组本身的赋值、添加、删除、更新数组的变化。数组项中的属性的赋值观察不到。
- 当装饰的对象是Data时,可以观察到Data整体的赋值,同时可通过调用Date的接口setFullYear, setMonth, setDate, setHours, setMinutes, setSeconds, setMilliseconds, setTime, setUTCFullYear, setUTCMonth, setUTCDate, setUTCHours, setUTCMinutes, setUTCSeconds, setUTCMilliseconds 更新Date的属性。
- 当装饰的变量是Map时,可以观察到Map整体的赋值,同时可通过调用Map的接口set、clear、delete更新Map的值。
- 当装饰的变量时Set时,可以观察到Set整体的赋值,同时可通过调用Set的接口add、clear、delete更新Set的值。
框架行为
- 当状态变量被修改时,查询依赖该状态变量的组件。
- 执行依赖该状态变量的组件的更新方法,组件更新渲染。
- 和该状态组件不相关的组件和UI描述不会重新渲染。
注意事项
箭头函数体内的this对象,就是定义该函数时所在的作用域指向的对象,而不是使用时所在的作用域指向的对象。 所以当使用箭头函数修改被装饰器@State代理的状态时,要注意。
例如在被@State修饰的class中,声明一个箭头函数,修改class中的某个属性,调用后不生效。
@Prop装饰器:父子单向同步
- @Prop装饰的变量可以和父组件建立单向的同步关系。
- @Prop装饰的变量是可变的,但是变化不会同步回其父组件。
- 当数据源更改时,@Prop装饰的变量都会更新,并且会覆盖本地所有更改。
限制条件
- @Prop装饰变量时会进行深拷贝,在拷贝的过程中除了基本类型、Map、Set、Data、Array外,都会丢失类型。
- @Prop装饰器不能在@Entry装饰的自定义组件中使用。
观察变化
- 当装饰的类型是允许的类型,即Object、class、string、number、boolean、enum类型都可以观察到赋值的变化。
- 当装饰的类型是Object或者class复杂类型时,可以观察到第一层属性的变化。如果嵌套class中的每一层都是被@Observed装饰的,可以观察到嵌套属性的变化。
- 当装饰的类型是数组时,可以观察到数组本身的赋值和数据项的添加、删除和更新。
- 当装饰的变量是Map时,可以观察到Map整体的赋值,同时可通过调用Map的接口set, clear, delete 更新Map的值。
- 当装饰的变量是Set时,可以观察到Set整体的赋值,同时可通过调用Set的接口add, clear, delete 更新Set的值。
对于@State和@Prop的同步场景:
- 使用父组件中@State变量初始化子组件中的@Prop变量。当@State变量变化时,该变量值也会同步更新至@Prop变量。
- @Prop装饰的变量修改,不会影响其数据源@State装饰的变量值。
- 除了@State,数据源也可以使用@Prop和@Link装饰,对@Prop的同步机制是一样的。
- 数据源和@Prop变量的类型需要相同,@Prop允许简单类型和class类型。
框架行为
要理解@Prop变量初始化和更新机制,需要了解父组件和拥有@Prop变量的子组件初始渲染和更新流程。
初始渲染:
- 执行父组件的build()函数将创建子组件的新实例,将数据源传递给子组件。
- 初始化子组件@Prop装饰的变量。
更新:
- 子组件@Prop更新时,更新仅停留在当前子组件,不会同步回父组件。
- 当父组件的数据源更新时,子组件的@Prop装饰的变量将会被来自父组件的数据源重置,所有@Prop装饰的本地的修改将被父组件的更新覆盖。
注意事项:
@Prop装饰的数据更新依赖其所属的自定义组件的重新渲染,所以在应用进入后台时,无法刷新。
如需后台刷新,可使用@Link代替。
@Link装饰器:父子双向同步
子组件中被@Link装饰的变量与其父组件中对应的数据源建立双向数据绑定。
限制条件
@Link装饰器不能在@Entry装饰的自定义组件中使用。
观察变化
- 当装饰的数据类型为boolean、string、number类型时,可以同步观察到数值的变化。
- 当装饰的数据类型为class或者Object时,可以观察到赋值和属性赋值的变化。
- 当装饰的数据类型为array时,可以观察到数组的添加、删除、更新数据项的变化。
- 当装饰的对象是Date时,可以观察到Date整体的赋值,同时可通过调用Date的接口setFullYear, setMonth, setDate, setHours, setMinutes, setSeconds, setMilliseconds, setTime, setUTCFullYear, setUTCMonth, setUTCDate, setUTCHours, setUTCMinutes, setUTCSeconds, setUTCMilliseconds 更新Date的属性。
- 当装饰的变量是Map时,可以观察到Map整体的赋值,同时可通过调用Map的接口set, clear, delete 更新Map的值。
- 当装饰的变量是Set时,可以观察到Set整体的赋值,同时可通过调用Set的接口add, clear, delete 更新Set的值。
框架行为
@Link装饰的变量和其所属的自定义组件共享生命周期。其初始渲染和双向更新流程如下
- 初始渲染:执行父组件的build()函数后将创建子组件的新实例。
- 必须指定父组件中的@State变量,用于初始化子组件的@Link变量。子组件的@Link变量值与其父组件的数据源变量保持同步。
- 父组件的@State状态变量包装类通过构造函数传递给子组件,子组件的@Link包装类拿到父组件的@State的状态变量后,将当前@Link包装类this指针注册给父组件的@State变量。
- @Link的数据源更新:即父组件中状态变量更新,引起相关子组件的@Link的更新。
- 通过初始渲染的步骤可知,子组件@Link包装类把当前this指针注册给父组件。父组件@State变量变更后,会遍历更新所有依赖它的系统组件和状态变量。
- 通知@Link包装类更新后,子组件中所有依赖@Link状态变量的系统组件都会被通知更新,以此来实现父组件对子组件的状态数据同步。
- @Link更新:当子组件中@Link更新后。
- @Link更新后,调用父组件的@State包装类的set方法,将更新后的数值同步回父组件。
- 子组件@Link和父组件@State分别遍历依赖的系统组件,进行对应的UI更新。以此来实现子组件@Link同步回父组件@State。
使用双向同步机制更改本地其他变量
使用@Watch
可以在双向同步时,更改本地变量。
@Entry
@Component
struct Parent {
@State sourceNumber: number = 0;
build() {
Column() {
Text(`父组件的sourceNumber:` + this.sourceNumber)
Child({ sourceNumber: this.sourceNumber })
Button('父组件更改sourceNumber')
.onClick(() => {
this.sourceNumber++;
})
}
.width('100%')
.height('100%')
}
}
@Component
struct Child {
@State memberMessage: string = 'Hello World';
@Link @Watch('onSourceChange') sourceNumber: number;
onSourceChange() {
this.memberMessage = this.sourceNumber.toString();
}
build() {
Column() {
Text(this.memberMessage)
Text(`子组件的sourceNumber:` + this.sourceNumber.toString())
Button('子组件更改memberMessage')
.onClick(() => {
this.memberMessage = 'Hello memberMessage';
})
}
}
}
上述例子中,修改子组件中的@State变量,只会刷新子组件,并不会影响到父组件。