赞
踩
在声明式 UI 框架中,数据的改变触发 UI 的重新渲染。在 ArkUI 中不是所有数据的变化都会触发 UI 重新渲染,只有 状态变量 才会引起 UI 重新渲染。
状态变量: 指被状态装饰器装饰的变量,只有这种变量的改变才会引起 UI 的重新渲染。
常规变量: 指没有被状态装饰器装饰的变量,不会引起 UI 的重新渲染。
按影响范围分为:
按传递方向分为:
@State 装饰的状态变量,是组件内的状态,是私有的,只能从组件内部访问,不与父组件中任何类型的状态变量同步。
允许装饰的变量类型:Object、class、string、number、boolean、enum,Date, 以及这些类型的数组。API11及以上支持Map、Set, undefined,null 和 联合类型
@Entry @Component struct StatePage { @State text: string = 'HarmonyOS' @State count: number = 0 build() { Column({ space: 10 }) { Text('string: ' + this.text).fontSize(17) Text('number: ' + this.count) Button('text = "Android"').onClick((event: ClickEvent) => { this.text = 'Android' }) Button('count++').onClick((event: ClickEvent) => { this.count++ }) } .width('100%') .height('100%') } }
点击 Button 修改了 text,count 变量的值,会触发 UI 重新渲染,显示最新的值。
export class User { name: string car?: Car constructor(name: string, car?: Car) { this.name = name; this.car = car; } } export class Car { brand: string engine?: Engine constructor(brand: string, engine?: Engine) { this.brand = brand; this.engine = engine; } } export class Engine { type?: string constructor(type: string) { this.type = type; } }
@Entry @Component struct StateClassPage { @State user: User = new User('Jack') build() { Column({space: 10}) { Text('Class: ' + `name=${this.user.name}, car=${this.user.car?.brand}`).fontSize(17) Column({ space: 10 }) { Button(`user = new User('Mike')`).onClick((event: ClickEvent) => { // 有效刷新 this.user = new User('Mike') }) Button(`user.name = 'Pony New'`).onClick((event: ClickEvent) => { // 有效刷新 this.user.name = 'Pony New' }) Button(`user.car = new Car('Benz')`).onClick((event: ClickEvent) => { // 有效刷新,@State 可以观察到一级属性 car 的赋值 this.user.car = new Car('Benz') }) Button(`this.user.car.brand = 'BMW'`).onClick((event: ClickEvent) => { // 无效刷新, user.car.brand, user.car.engine 为二级属性, @State 观察不到, 需要使用 @Observed 和 @ObjectLink if (this.user.car) { this.user.car.brand = 'BMW' } }) } }.width('100%') } }
对于 @State 装饰的 Class 状态变量, ArkUI 只能观察到状态变量的一级属性的变化,从而引起 UI 重新渲染。如上例中 user.name 和 user.car 是 User 的一级属性,user.car.brand 是 User 的二级属性, 所以点击Button执行 this.user.car.brand = 'BMW'
UI 没有刷新,这种情况,需要使用 @Observed/@ObjectLink
@Entry @Component struct StateArrayPage { @State stringArray: string[] = ['Java', 'Kotlin'] @State userArray: User[] = [new User('Jack'), new User('Make')] build() { Column({ space: 10 }) { ForEach(this.stringArray, (item: string) => { Text(item) }) Row({ space: 6 }) { Button('add').onClick((event: ClickEvent) => { this.stringArray.push('Typescript') }) Button('remove').onClick((event: ClickEvent) => { if (this.stringArray.length > 0) { this.stringArray.splice(0, 1) } }) Button('update').onClick((event: ClickEvent) => { if (this.stringArray.length > 0) { this.stringArray[0] = 'Javascript by updated' } }) Button('new Array()').onClick((event: ClickEvent) => { this.stringArray = ['New Array1', 'New Array2'] }) } Divider().height(20).color(Color.Gray) ForEach(this.userArray, (item: User) => { Text(`name=${item.name},car=${item.car}`) }) Column({ space: 10 }) { Button('add').onClick((event: ClickEvent) => { this.userArray.push(new User('Jane')) }) Button('remove').onClick((event: ClickEvent) => { if (this.stringArray.length > 0) { this.userArray.splice(0, 1) } }) Button('update userArray[0]=new User("Lace")').onClick((event: ClickEvent) => { if (this.stringArray.length > 0) { // class 数组, 可以观察到数组中元素 重新赋值 this.userArray[0] = new User('Lace') } }) Button('userArray[0].name="Shane"').onClick((event: ClickEvent) => { // 无效刷新, 对于 class数组, 观察不到数组中 元素的属性 赋值 this.userArray[0].name = 'Shane' }) } } .width('100%') } }
对于数组,ArkUI 可以观察到 数组新增,删除,重新创建数组, 数组项重新赋值,从而引起 UI 重新渲染,无法观察到 数组中某个元素的 属性的变化
@Prop 是单向同步的:对父组件状态变量值的修改,将同步给子组件@Prop装饰的变量,子组件@Prop变量的修改不会同步到父组件的状态变量上。
@Entry @Component struct PropPage { @State text: string = 'ArkUI' @State user: User = new User('Jane', new Car('Benz E')) build() { Column({ space: 10 }) { Text(this.text).onClick(() => { this.text = 'Flutter ' }) Text(`name:${this.user.name},car=${this.user.car?.brand}`) .onClick(()=> { this.user = new User('parent User') }) Divider().height(20) PropChild({ title: this.text, user: this.user }) }.width('100%') } } @Component struct PropChild { /** * 修改父组件的状态变量可以同步刷新子组件, 而修改子组件的状态变量不会同步刷新父组件 */ @Prop title: string = 'default' @Prop user: User build() { Column({ space: 10 }) { Text(this.title).onClick(() => { this.title = 'JetpackCompose' }) Text(`name:${this.user.name},car=${this.user.car?.brand}`) Button('user=new User("Shine")').onClick((event: ClickEvent) => { this.user = new User('Shine') }) Button('user.car = new Car("Audi A6")').onClick((event: ClickEvent) => { this.user.car = new Car('Audi A6') }) Button('user.car.brand="BWM 530"').onClick((event: ClickEvent) => { if (this.user.car) { this.user.car.brand = 'BWM 530' } }) } } }
@Prop 装饰的状态变量, ArkUI 同样观察不到 二级属性的变化,并且子组件状态变量的变化不会引起父组件 UI 的刷新, 但父组件状态变量的变化可以引起子组件的刷新。
子组件中被@Link装饰的变量与其父组件中对应的数据源建立双向数据绑定.
@Link装饰器不能在@Entry装饰的自定义组件中使用。
@Entry @Component struct LinkBasicPage { @State text: string = 'ArkUI' build() { Column({ space: 10 }) { Text('父组件:' + this.text).onClick(() => { this.text = 'Flutter ' }) LinkBasicChild({ title: $text }) }.width('100%') } } @Component struct LinkBasicChild { // @Link装饰的状态变量禁止初始化 // @Link title: string = '' @Link title: string build() { Column({ space: 10 }) { Text('子组件:' + this.title).onClick(() => { this.title = 'JetpackCompose' }) } } }
父子组件的状态变量的变化,可以相互引起 UI 刷新
@Entry @Component struct LinkClassPage { @State user: User = new User('Jane', new Car('Benz E')) build() { Column({ space: 10 }) { Text(`父组件 name=${this.user.name},car=${this.user.car?.brand}`) Button('user=new User("Mike")').onClick((event: ClickEvent) => { this.user = new User('Mike-P') }) Divider() LinkClassChild({ user: $user }) }.width('100%') } } @Component struct LinkClassChild { // 禁止本地初始化 @Link user: User build() { Column({ space: 10 }) { Text(`子组件 name=${this.user.name},car=${this.user.car?.brand}`) Button('user=new User("Shine")').onClick((event: ClickEvent) => { this.user = new User('Shine') }) Button('user.car = new Car("Audi A6")').onClick((event: ClickEvent) => { this.user.car = new Car('Audi A6') }) Button('user.car.brand="BWM 530"').onClick((event: ClickEvent) => { if (this.user.car) { this.user.car.brand = 'BWM 530' } }) } } }
@Link 装饰的状态变量, ArkUI 同样观察不到 二级属性的变化。一级属性可以引起父子组件UI 的刷新。
@Entry @Component struct LinkArrayPage { @State list: string[] = ['Android', 'iOS', 'Harmony'] build() { Column({ space: 10 }) { ForEach(this.list, (item: string) => { Text(item) }) Button('new Array').onClick((event: ClickEvent) => { this.list = new Array<string>('Benz', 'BMW', 'Audi') }) Button('Add').onClick((event: ClickEvent) => { this.list.push('parent add') }) Button('remove').onClick((event: ClickEvent) => { if (this.list.length > 0) { this.list.splice(0, 1) } }) Button('update').onClick((event: ClickEvent) => { if (this.list.length > 0) { this.list[0] = 'parent update' } }) Divider() LinkArrayChild({ childList: $list }) }.width('100%') } } @Component struct LinkArrayChild { @Link childList: string[] build() { Column({ space: 10 }) { ForEach(this.childList, (item: string) => { Text(item) }) Button('new Array').onClick((event: ClickEvent) => { this.childList = new Array<string>('Kotlin', 'Swift', 'ArkTS') }) Button('Add').onClick((event: ClickEvent) => { this.childList.push('child Add') }) Button('remove').onClick((event: ClickEvent) => { if (this.childList.length > 0) { this.childList.splice(0, 1) } }) Button('update').onClick((event: ClickEvent) => { if (this.childList.length > 0) { this.childList[0] = 'child update' } }) } } }
@Provide和@Consume,应用于与后代组件的双向数据同步,应用于状态数据在多个层级之间传递的场景。不同于上文提到的父子组件之间通过命名参数机制传递,@Provide和@Consume摆脱参数传递机制的束缚,实现跨层级传递。
@Entry @Component struct ProvideConsumeBasicPage { @Provide count: number = 1 build() { Column({ space: 10 }) { Text(this.count.toString()).onClick(() => { this.count++ }) ProvideChild() } .width('100%') } } @Component struct ProvideChild { @Consume count: number build() { Column({ space: 10 }) { Text(this.count.toString()).onClick(() => { this.count += 2 }) ProvideChildSecond() } .backgroundColor(Color.Brown) .padding(30) } } @Component struct ProvideChildSecond { @Consume count: number build() { Column() { Text(this.count.toString()).onClick(() => { this.count += 3 }) } .backgroundColor(Color.Pink) .padding(30) } }
@Entry @Component struct ProvideConsumeClassPage { @Provide user: User = new User('Apple') build() { Column({ space: 10 }) { Text('父组件:' + this.user.name) Button('user.name="parent"').onClick((event: ClickEvent) => { this.user.name = 'parent' }) Divider() ProvideClassChild() }.width('100%') } } @Component struct ProvideClassChild { @Consume user: User build() { Column({ space: 10 }) { Text('孩子组件:' + this.user.name) Divider() ProvideClassChildSecond() } } } @Component struct ProvideClassChildSecond { @Consume user: User build() { Column({ space: 10 }) { Text('孙子组件:' + this.user.name) Divider() ProvideClassChildThird() } } } @Component struct ProvideClassChildThird { @Consume user: User build() { Column({ space: 10 }) { Text('曾孙子:' + this.user.name) Button('user=new User("曾孙子")').onClick((event: ClickEvent) => { this.user = new User('曾孙子') }) Button('user.name="曾孙name:sx"').onClick((event: ClickEvent) => { this.user.name="曾孙name:sx" }) Button('user.car.brand = "曾孙.name.car: Benz S"').onClick((event: ClickEvent) => { if (this.user.car) { this.user.car.brand = '曾孙.name.car: Benz S' } }) } } }
上述 @State, @Prop,@Link 和 @Provide/@Consume 装饰器只能观察 class 一级属性赋值,无法观察二级属性变化,但在实际开发过程中,class 的属性类型仍是 class 类型是很常用见的,因此就需要使用 @Observed/@ObjectLink 来实现了。
@ObjectLink和@Observed类装饰器用于在涉及嵌套对象或数组的场景中进行双向数据同步:
@Entry @Component struct ObjectLinkBasicPage { @State user: User = new User('Jack', new Car('BMW 3 serial')) build() { Column({ space: 10 }) { Button(`user.car.brand = 'BMW'`).onClick((event: ClickEvent) => { if (this.user.car) { this.user.car.brand = 'Benz E class' } }) Divider() CarItem({ car: this.user.car }) }.width('100%') } } @Component struct CarItem { // Car 需要被 @Observed 装饰 @ObjectLink car: Car build() { Column() { Text('car: ' + this.car.brand) Button('car=new Car("audi")').onClick((event: ClickEvent) => { // 报错,@ObjectLink装饰的状态变量不允许被赋值, 可以对它的属性赋值 // this.car = new Car('audi') this.car.brand = 'audi' }) } .width('90%') .padding(20) .backgroundColor(Color.Pink) } }
@Entry @Component struct ObjectLinkBasicPage { @State userArray: User[] = [ new User('Jack', new Car('BMW 3 serial')), new User('Mike', new Car('Audi A4L')) ] build() { Column({ space: 10 }) { Button(`userArray[0].car.brand = 'Benz S class'`).onClick((event: ClickEvent) => { if (this.userArray.length > 0) { let user: User = this.userArray[0] if (user.car) { user.car.brand = 'Benz S class' } } }) ForEach(this.userArray, (item: User) => { UserItem({ user: item }) }) }.width('100%') } } @Component struct UserItem { // User 需要被 @Observed 装饰 @ObjectLink user: User build() { CarItem({ car: this.user.car }) } } @Component struct CarItem { // Car 需要被 @Observed 装饰 @ObjectLink car: Car build() { Column() { Text('car: ' + this.car.brand) } .width('90%') .padding(20) .backgroundColor(Color.Pink) } }
@Entry @Component struct ViewModelPage { @State viewModel: UserViewModel = new UserViewModel([ new User('jack', new Car('保时捷')), new User('mack', new Car('奔驰')) ]) build() { Scroll() { Column() { Flex({ wrap: FlexWrap.Wrap }) { Button('new Array') .margin(6) .onClick((event: ClickEvent) => { this.viewModel.userList = [new User('new kotlin')] }) Button('add') .margin(6) .onClick((event: ClickEvent) => { this.viewModel.userList.push(new User('kotlin')) }) Button('remove') .margin(6) .onClick((event: ClickEvent) => { if (this.viewModel.userList.length > 0) { this.viewModel.userList.splice(0, 1) } }) Button('viewModel.userList[0]=new User("peter")') .margin(6) .onClick((event: ClickEvent) => { if (this.viewModel.userList.length > 0) { this.viewModel.userList[0] = new User('peter', new Car('马自达')) } }) Button('viewModel.userList[0].name="张三"') .margin(6) .onClick((event: ClickEvent) => { if (this.viewModel.userList.length > 0) { this.viewModel.userList[0].name = '张三' } }) Button('viewModel.userList[0].car.brand="XIAOMI SU7 MAX"') .margin(6) .onClick((event: ClickEvent) => { if (this.viewModel.userList.length > 0) { let car = this.viewModel.userList[0].car if (car) { car.brand = 'XIAOMI SU7 MAX' } } }) } ChildUser({ userList: this.viewModel.userList }) } } } } @Component struct ChildUser { // ObservableArray 需要被 @Observed 装饰 @ObjectLink userList: ObservableArray<User> build() { Column() { ForEach(this.userList, (user: User) => { UserItem({ user: user }) Divider() }) }.padding(20) } } @Component struct UserItem { // User 需要被 @Observed 装饰 @ObjectLink user: User build() { Column() { Text('user.name: ' + this.user.name) CarItem({ car: this.user.car }) } } } @Component struct CarItem { // Car 需要被 @Observed 装饰 @ObjectLink car: Car | undefined build() { Text('user.car: ' + this.car?.brand) } } @Observed export class ObservableArray<T> extends Array<T> { constructor(args: T[]) { if (args instanceof Array) { super(...args); } else { super(args) } } }
从 API12 开始,更推荐使用@ObservedV2装饰器和@Trace装饰器装饰类以及类中的属性,来管理状态。
@ObservedV2 class Father { @Trace name: string = "Tom"; } class Son extends Father { } @Entry @Component struct Index { son: Son = new Son(); build() { Column() { // 当点击改变name时,Text组件会刷新 Text(`${this.son.name}`) .onClick(() => { this.son.name = "Jack"; }) } } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。