#星光计划2.0# HarmonyOS ArkUI之UI状态管理(TS) 原创 精华
作者:王国菊
【本文正在参与51CTO HarmonyOS技术社区创作者激励计划-星光计划2.0】
前言
在声明式UI编程范式中,UI是应用程序状态的函数,开发人员通过修改当前应用程序状态来更新相应的UI界面。
开发框架提供了多种应用程序状态管理的能力。
状态变量装饰器
@State:组件拥有的状态属性。每当@State装饰的变量更改时,组件会重新渲染更新UI。
@Link:组件依赖于其父组件拥有的某些状态属性。每当任何一个组件中的数据更新时,另一个组件的状态都会更新,父子组件都会进行重新渲染。
@Prop:工作原理类似@Link,只是子组件所做的更改不会同步到父组件上,属于单向传递。
@state
@State装饰的变量是组件内部的状态数据,当这些状态数据被修改时,将会调用所在组件的build方法进行UI刷新。
@State状态数据具有以下特征:
支持多种类型:允许如下强类型的按值和按引用类型:class、number、boolean、string,以及这些类型的数组,即Array<class>、Array<string>、Array<boolean>、Array<number>。不允许object和any。
支持多实例:组件不同实例的内部状态数据独立。
内部私有:标记为@State的属性不能直接在组件外部修改。它的生命周期取决于它所在的组件。
需要本地初始化:必须为所有@State变量分配初始值,将变量保持未初始化可能导致框架行为未定义。
创建自定义组件时支持通过状态变量名设置初始值:在创建组件实例时,可以通过变量名显式指定@State状态属性的初始值。
复杂类型的状态属性示例
@Entry
@Component
struct MyComponent {
@State count: number = 0
// MyComponent provides a method for modifying the @State status data member.
private toggleClick() {
this.count += 1
}
build() {
Column() {
Button() {
Text(`click times: ${this.count}`)
.fontSize(10)
}.onClick(this.toggleClick.bind(this))
}
}
}
简单类型的状态属性示例
// Customize the status data class.
class Model {
value: string
constructor(value: string) {
this.value = value
}
}
@Entry
@Component
struct EntryComponent {
build() {
Column() {
MyComponent({count: 1, increaseBy: 2}) // MyComponent1 in this document
MyComponent({title: {value: 'Hello, World 2'}, count: 7}) //MyComponent2 in this document
}
}
}
@Component
struct MyComponent {
@State title: Model = {value: 'Hello World'}
@State count: number = 0
private toggle: string = 'Hello World'
private increaseBy: number = 1
build() {
Column() {
Text(`${this.title.value}`)
Button() {
Text(`Click to change title`).fontSize(10)
}.onClick(() => {
this.title.value = this.toggle ? 'Hello World' : 'Hello UI'
}) // Modify the internal state of MyComponent using the anonymous method.
Button() {
Text(`Click to increase count=${this.count}`).fontSize(10)
}.onClick(() => {
this.count += this.increaseBy
}) // Modify the internal state of MyComponent using the anonymous method.
}
}
}
在上述示例中:
用户定义的组件MyComponent定义了@State状态变量count和title。如果count或title的值发生变化,则执行MyComponent的build方法来重新渲染组件;
EntryComponent中有多个MyComponent组件实例,第一个MyComponent内部状态的更改不会影响第二个MyComponent;
@prop
@Prop具有与@State相同的语义,但初始化方式不同。@Prop装饰的变量必须使用其父组件提供的@State变量进行初始化,允许组件内部修改@Prop变量,但上述更改不会通知给父组件,即@Prop属于单向数据绑定。
@Prop状态数据具有以下特征:
支持简单类型:仅支持简单类型:number、string、boolean;
私有:仅在组件内访问;
支持多个实例:一个组件中可以定义多个标有@Prop的属性;
创建自定义组件时将值传递给$\color{red}{@Prop}$ 变量进行初始化:在创建组件的新实例时,必须初始化所有@Prop变量,不支持在组件内部进行初始化。(创建新组件实例时,必须初始化其所有@Prop变量。)
示例
@Entry
@Component
struct ParentComponent {
@State countDownStartValue: number = 10 // 10 Nuggets default start value in a Game
build() {
Column() {
Text(`Grant ${this.countDownStartValue} nuggets to play.`)
Button() {
Text('+1 - Nuggets in New Game')
}.onClick(() => {
this.countDownStartValue += 1
})
Button() {
Text('-1 - Nuggets in New Game')
}.onClick(() => {
this.countDownStartValue -= 1
})
CountDownComponent({ count: this.countDownStartValue, costOfOneAttempt: 2})
}
}
}
@Component
struct CountDownComponent {
@Prop count: number
private costOfOneAttempt: number
build() {
Column() {
if (this.count > 0) {
Text(`You have ${this.count} Nuggets left`)
} else {
Text('Game over!')
}
Button() {
Text('Try again')
}.onClick(() => {
this.count -= this.costOfOneAttempt
})
}
}
}
在上述示例中,当按“+1”或“-1”按钮时,父组件状态发生变化,重新执行build方法,此时将创建一个新的CountDownComponent组件。父组件的countDownStartValue状态属性被用于初始化子组件的@Prop变量。当按下子组件的“Try again”按钮时,其@Prop变量count将被更改,这将导致CountDownComponent重新渲染。但是,count值的更改不会影响父组件的countDownStartValue值。
@link
@Link装饰的变量可以和父组件的@State变量建立双向数据绑定:
支持多种类型:@Link变量的值与@State变量的类型相同,即class、number、string、boolean或这些类型的数组;
私有:仅在组件内访问;
单个数据源:初始化@Link变量的父组件的变量必须是@State变量;
双向通信:子组件对@Link变量的更改将同步修改父组件的@State变量;
创建自定义组件时需要将变量的引用传递给@Link变量:在创建组件的新实例时,必须使用命名参数初始化所有@Link变量。@Link变量可以使用@State变量或@Link变量的引用进行初始化。@State变量可以通过’$'操作符创建引用。(变量不能在组件内部进行初始化。)
示例
@Link和@State、@Prop结合使用示例
@Entry
@Component
struct ParentView {
@State counter: number = 0
build() {
Column() {
ChildA({counterVal: this.counter}) // pass by value
ChildB({counterRef: $counter}) // $ creates a Reference that can be bound to counterRef
}
}
}
@Component
struct ChildA {
@Prop counterVal: number
build() {
Button() {
Text(`ChildA: (${this.counterVal}) + 1`)
}.onClick(() => {this.counterVal+= 1})
}
}
@Component
struct ChildB {
@Link counterRef: number
build() {
Button() {
Text(`ChildB: (${this.counterRef}) + 1`)
}.onClick(() => {this.counterRef+= 1})
}
}
上述示例中,ParentView包含ChildA和ChildB两个子组件,ParentView的状态变量counter分别初始化ChildA和ChildB:
- ChildB使用@Link建立双向状态绑定;
- 当ChildB修改counterRef状态变量值时,该更改将同步到ParentView和ChildA共享;
- ChildA使用@Prop建立从ParentView到自身的单向状态绑定;
- 当ChildA修改状态时,ChildA将重新渲染,但该更改不会传达给ParentView和ChildB。
更多原创内容请关注:深开鸿技术团队
入门到精通、技巧到案例,系统化分享HarmonyOS开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。
很不错的内容,从原理上讲开了,很深入的内容