组件状态管理
关注’猿来编码‘,微信订阅号,回复 ’组件状态‘,获取详细
文章目录
一、@State
@State用于装饰当前组件的状态变量,@State装饰的变量在发生变化时,会驱动当前组件的视图刷新,语法如下:
@State count:number = 1;
需要注意的是:@State装饰的变量必须进行本地初始化。
允许装饰的类型
基本类型:string、number、boolean、enum
对象类型:json对象、class的实例对象、
数组类型:上面所有类型的数组
能够观察到的变化
注意:
并不是状态变量的所有更改都会引起UI的刷新
只有可以被框架观察到的修改才会引起UI刷新
属性本身的改变都可以 (无论什么类型)
对象:能监视对象的直接属性变化,不能监视嵌套属性的改变
数组:能监视数组中元素的变化,不能监视元素对象内部的变化
测试
定义对象和数组的状态和显示数据
@State @Watch('onChange')obj: {a: {b: number}} = {a: {b: 1}} @State @Watch('onChange2')arr: {a: number}[] = [{a: 1}]
Text('state.obj' + JSON.stringify(this.obj)).fontSize(18) Text('state.arr' + JSON.stringify(this.arr)).fontSize(18)
修改属性对象
this.obj = {a: {b: 2}} // 修改属性本身 => 能监视 this.obj.a = {b: 3} // 修改属性对象的直接属性 =》 能监视 this.obj.a.b = 4 // 修改属性对象的嵌套属性 =》 不能监视到
修改属性数组
this.arr = [] // 修改属性本身 => 能监视 this.arr[0] = {a: 2} // 修改属性数组的元素 => 能监视 this.arr.push({a: 3}) // 修改属性数组的元素 => 能监视 this.arr[0].a = 4 // 修改属性数组的元素对象内部属性 =》 不能监视
二、@Prop
@Prop用于装饰子组件的状态变量,@Prop装饰的变量会同步父组件的状态,但只能单向同步,也就是父组件的状态变化会自动同步到子组件,而子组件的变化不会同步到父组件。
父组件
@Entry @Component struct Parent{ @State count:number = 1; build(){ Column(){ Child({count:this.count}); } } }
子组件
@Component export struct Child{ @Prop count:number; build(){ Text('prop.count: ' + this.count); } }
需要注意的是:@Prop装饰的变量不允许本地初始化,只能通过父组件传参进行初始化。
允许装饰的类型
官方文档:只允许基本类型,不允许对象和数组
实际情况:与@State一致,可以是对象、数组
能够观察到的变化
与@State一致
三、@Link
@Link用于装饰子组件的状态变量,@Prop变量同样会同步父组件状态,但是能够双向同步。也就是父组件的变化会同步到子组件,而子组件的变化也会同步到父组件。
父组件
@Entry @Component struct Parent{ @State count:number = 1; build(){ Column(){ Child({count: $count}); } } }
子组件
@Component export struct Child{ @Link count:number; build(){ Text('link.count: ' + this.count); } }
需要注意的是:@Link装饰的变量不允许本地初始化,只能由父组件通过传参进行初始化,并且父组件必须使用$变量名的方式传参,以表示传递的是变量的引用。
允许装饰的类型
与@State一致
框架能够观察到的变化
与@State一致
四、@Provide 与 @Consume
@Provide和@Consume用于跨层级传递状态信息,其中@Provide用于装饰祖先组件的状态变量,@Consume用于装饰后代组件的状态变量。可以理解为祖先组件提供(Provide)状态信息供后代组件消费(Consume),并且祖先和后代的状态信息可以实现双向同步。
注意:
@Provide装饰变量必须本地初始化,而@Consume装饰的变量不允许本地初始化。
@Provide & @Consume处理的状态数据是双向同步的
祖先组件
@Entry @Component struct GrandParent { @Provide count: number = 1; @Provide('msg') message: string = '老A'; build() { Column() { ... } } }
后代组件
@Entry @Component export struct Child { @Consume count: number; @Consume('msg') childMsg: string; build() { Column() { Text('Consume.count: ' + this.count); Text('Consume.childMsg: ' + this.childMsg); } } }
允许装饰的类型
与@State一致
能够观察到的变化
与@State一致
测试:
@Component export default struct Child1 { @Prop obj1: {a: {b: number}} @Prop arr1: {a: number}[] update() { // this.obj1 = {a: {b: 3}} // 修改属性本身 => 能监视 // this.obj1.a = {b: 4} // 修改属性对象的直接属性 =》 能监视 // setTimeout(() => { // this.obj1.a.b = 9 // 修改属性对象的嵌套属性 =》 不能监视到 报错(且会报错,导致程序退出) // }, 1000) // this.arr1 = [] // 修改属性本身 => 能监视 // this.arr1[0] = {a: 5} // 修改属性数组的元素 => 能监视 this.arr1.push({a: 8}) // 修改属性数组的元素 => 能监视 setTimeout(() => { this.arr1[0].a = 5 // 修改属性数组的元素对象内部属性 =》 不能监视(且会报错,导致程序退出) }, 1000) } build() { Column({space: 10}) { Text('prop.obj' + JSON.stringify(this.obj1)).fontSize(18) Text('prop.arr' + JSON.stringify(this.arr1)).fontSize(18) Button('开始更新 prop').onClick(() => this.update()) } .width('100%') .padding(20) .border({width: 1, color: Color.Gray}) } }
@Component export default struct Child2 { @Link obj2: {a: {b: number}} @Link arr2: {a: number}[] update () { // this.obj1 = {a: {b: 2}} // 修改属性本身 => 能监视 // this.obj2.a = {b: 4} // 修改属性对象的直接属性 =》 能监视 // setTimeout(() => { // this.obj2.a.b = 9 // 修改属性对象的嵌套属性 =》 不能监视到 // }) // this.arr2 = [] // 修改属性本身 => 能监视 // this.arr2[0] = {a: 3} // 修改属性数组的元素 => 能监视 this.arr2.push({a: 5}) // 修改属性数组的元素 => 能监视 setTimeout(() => { this.arr2[0].a = 4 // 修改属性数组的元素对象内部属性 =》 不能监视 }) } build() { Column({space: 10}) { Text('link.obj2' + JSON.stringify(this.obj2)).fontSize(18) Text('link.arr2' + JSON.stringify(this.arr2)).fontSize(18) Button('开始更新 link').onClick(() => this.update()) } .width('100%') .padding(20) .border({width: 1, color: Color.Gray}) } }
import Child1 from './Child1' import Child2 from './Child2' @Entry @Component struct StateTest { @State obj: {a: {b: number}} = {a: {b: 1}} @State arr: {a: number}[] = [{a: 1}] update() { // this.obj = {a: {b: 1}} // 修改属性本身 => 能监视 // this.obj.a = {b: 2} // 修改属性对象的直接属性 =》 能监视 // setTimeout(() => { // this.obj.a.b = 6 // 修改属性对象的嵌套属性 =》 不能监视到 // }, 1000) // this.arr = [] // 修改属性本身 => 能监视 // this.arr[0] = {a: 2} // 修改属性数组的元素 => 能监视 this.arr.push({a: 3}) // 修改属性数组的元素 => 能监视 setTimeout(() => { this.arr[0].a = 9 // 修改属性数组的元素对象内部属性 =》 不能监视 }, 1000) } build() { Column({space: 10}) { Text('state.obj' + JSON.stringify(this.obj)).fontSize(18) Text('state.arr' + JSON.stringify(this.arr)).fontSize(18) Button('开始更新2 state').onClick(() => this.update()) Child1({obj1: this.obj, arr1: this.arr}) Child2({obj2: $obj, arr2: $arr}) } .width('100%') .padding(20) } }
五、@Watch
用来监视状态数据的变化,包括:@State、@Prop、@Link、@Provide、@Consume
一旦状态数据变化,监视的回调就会调用
我们可以在监视的回调中执行应用需要的特定逻辑
以@State为例编码
@State @Watch('onCountChange') count: number = 0 /** * 一旦count变化,此回调函数就会自动调用 * @param name 被监视的状态属性名 */ onCountChange (name) { // 可以在此做特定处理 }
测试
@Component export default struct Child1 { @Prop count1: number build() { Column({space: 10}) { Row({space: 10}) { Text('prop.count1: ' + this.count1).fontSize(18) Button('更新prop.count1').onClick(() => this.count1 += 1) } } .width('100%') .padding(20) .border({width: 1, color: Color.Gray}) } }
@Component export default struct Child2 { @Link count2: number build() { Column({space: 10}) { Row({space: 10}) { Text('link.count2: ' + this.count2).fontSize(18) Button('开始更新link.count2').onClick(() => this.count2 += 1) } } .width('100%') .padding(20) .border({width: 1, color: Color.Gray}) } }
import GrandChild from './GrandChild' @Component export default struct Child3 { build() { Column({space: 10}) { GrandChild() } .width('100%') .padding(20) .border({width: 1, color: Color.Gray}) } }
import promptAction from '@ohos.promptAction' @Component export default struct GrandChild { @Consume @Watch('onMsgChange') msg: string onMsgChange () { promptAction.showToast({message: this.msg}) } build() { Column({space: 10}) { Text('Consume.msg: ' + this.msg).fontSize(18) Button('开始更新Consume.count2').onClick(() => this.msg += '--') } .width('100%') .padding(20) .border({width: 1, color: Color.Gray}) } }
import Child1 from './Child1' import Child2 from './Child2' import promptAction from '@ohos.promptAction' import Child3 from './Child3' @Entry @Component struct StateBaseTest { @State @Watch('onCountChange') count: number = 0 @Provide msg: string = 'abc' /** * 一旦count变化,此回调函数就会自动调用 * @param name 被监视的状态属性名 */ onCountChange (name) { if (this.count>3) { promptAction.showToast({message: `当前count为${this.count},已经超过了3`}) } } build() { Column({space: 10}) { Row({space: 10}) { Text('state.count: ' + this.count) .fontSize(18) Button('更新state.count').onClick(() => this.count += 1) } Text('count值超过3,每次更新都提示一下') .fontColor(Color.Orange) Child1({count1: this.count}) Child2({count2: $count}) Divider() Text('provide.msg: ' + this.msg) .fontSize(18) Button('开始更新provide.msg').onClick(() => this.msg += '++') Child3() } .width('100%') .padding(20) } }
六、@ObjectLink 和 @Observed
前面的问题:
● 属性对象中的嵌套对象的属性修改不能监视到,也就不会自动更新UI
● 属性数组中的元素对象的属性修改不能监视到,也就不会自动更新UI
● @Props与@Link声明接收的属性,必须是@State的属性,而不能是@State属性对象中嵌套的属性
解决办法
● 将嵌套对象的类型用class定义, 并使用@Observed来装饰
● 子组件中定义的嵌套对象的属性, 使用@ObjectLink来装饰
测试:
@Observed class Person2 { id: number; name: string; age: number; constructor(id, name, age) { this.id = id this.name = name this.age = age } } @Component struct PersonItem { // @Prop person: Person // @Link person: Person @ObjectLink person: Person2 build() { Row() { Text(JSON.stringify(this.person)) .fontSize(20) Button('更新年龄') .onClick(() => this.person.age += 2) } .border({width: 1, color: Color.Gray}) .padding(10) } } @Entry @Component struct PersonList { @State persons: Person2[] = [ new Person2(1, 'Tom', 12), new Person2(2, 'Jack', 13), ] build() { Column({space: 10}){ Button('更新嵌套对象的属性:一个人的年龄') .onClick(() => { this.persons[0].age++ }) List() { ForEach(this.persons, (item: Person2, index: number) => { ListItem(){ PersonItem({person: item}) } }) } } .padding(20) } }