#星光计划2.0# HarmonyOS ArkUI之UI状态管理(TS) 原创 精华

中软HOS小鸿
发布于 2021-12-27 11:07
浏览
2收藏

作者:王国菊
【本文正在参与51CTO HarmonyOS技术社区创作者激励计划-星光计划2.0】

前言

在声明式UI编程范式中,UI是应用程序状态的函数,开发人员通过修改当前应用程序状态来更新相应的UI界面。

开发框架提供了多种应用程序状态管理的能力。

#星光计划2.0# HarmonyOS ArkUI之UI状态管理(TS)-鸿蒙开发者社区

状态变量装饰器

@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))
        }
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

简单类型的状态属性示例

// 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.
        }
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.

在上述示例中:

用户定义的组件MyComponent定义了@State状态变量count和title。如果count或title的值发生变化,则执行MyComponent的build方法来重新渲染组件;
EntryComponent中有多个MyComponent组件实例,第一个MyComponent内部状态的更改不会影响第二个MyComponent;

@prop

@Prop具有与@State相同的语义,但初始化方式不同。@Prop装饰的变量必须使用其父组件提供的@State变量进行初始化,允许组件内部修改@Prop变量,但上述更改不会通知给父组件,即@Prop属于单向数据绑定。

@Prop状态数据具有以下特征:

支持简单类型:仅支持简单类型:number、string、boolean;

私有:仅在组件内访问;

支持多个实例:一个组件中可以定义多个标有@Prop的属性;

创建自定义组件时将值传递给@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.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.

在上述示例中,当按“+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})
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.

上述示例中,ParentView包含ChildA和ChildB两个子组件,ParentView的状态变量counter分别初始化ChildA和ChildB:

  • ChildB使用@Link建立双向状态绑定;
  • 当ChildB修改counterRef状态变量值时,该更改将同步到ParentView和ChildA共享;
  • ChildA使用@Prop建立从ParentView到自身的单向状态绑定;
  • 当ChildA修改状态时,ChildA将重新渲染,但该更改不会传达给ParentView和ChildB。

更多原创内容请关注:深开鸿技术团队

入门到精通、技巧到案例,系统化分享HarmonyOS开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2021-12-28 09:12:01修改
2
收藏 2
回复
举报
2
1
2
1条回复
按时间正序
/
按时间倒序
宜恒爸爸
宜恒爸爸

很不错的内容,从原理上讲开了,很深入的内容

1
回复
2022-3-1 15:22:52


回复
    相关推荐