组件状态管理

关注’猿来编码‘,微信订阅号,回复 ’组件状态‘,获取详细




一、@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)
  }
}

在这里插入图片描述

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐