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

前言

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

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

1.png

状态变量装饰器

@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开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。

想了解更多关于鸿蒙的内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com/#bkwz

::: hljs-center

21_9.jpg

:::