Skip to content

0x02d-鸿蒙开发之Observed使用

@ObjectLink和@Observed类装饰器用于在涉及嵌套对象或数组的场景中进行双向数据同步:

@Status

  • 需要默认初始化
  • @State装饰的变量支持初始化子组件的常规变量、@State、@Link、@Prop、@Provide

@status可以修饰什么

Object、class、string、number、boolean、enum类型,以及这些类型的数组。嵌套类型的场景请参考观察变化
类型必须被指定
不支持any,不支持简单类型和复杂类型的联合类型,不允许使undefined和null。

可以这么理解:@status 支持监听基本的类型和简单的嵌套。基本类型的监听不用说了。稍微复杂的,举例说明:

  • 见高亮的代码,监听嵌套模型的属性失败
  • 这里绑定object时,使用class或interface描述类型均可
ts
@Component  
@Preview  
@Entry  
struct QuickDevView {  
  @State model: FatherModel = { name: '' }  
  build() {  
    Column() {  
      Text("SuperView:" + this.model.name)    
      Column() {  
        Text("Child Name:" + (this.model.child?.name ?? ''))  
        Text("Child Age:" + (this.model.child?.age ?? ''))  
      }
      Button("sss")  
        .onClick(() => {  
          this.model.name = '老王'  
          if (!this.model.child) {  
            // 这个监听生效
            this.model.child = { age: 12, name: '小王' }  
          } else {  
            // 这个嵌套类型的监听无效  
            this.model.child.name = '小李'  
          }  
        })  
        .margin({ top: 10 })   
    }
  }  
}  
  
export interface FatherModel {  
  name: string  
  child ?: ChildModel  
}
  
export interface ChildModel {  
  age ?: number  
  name ?: string  
}
@Component  
@Preview  
@Entry  
struct QuickDevView {  
  @State model: FatherModel = { name: '' }  
  build() {  
    Column() {  
      Text("SuperView:" + this.model.name)    
      Column() {  
        Text("Child Name:" + (this.model.child?.name ?? ''))  
        Text("Child Age:" + (this.model.child?.age ?? ''))  
      }
      Button("sss")  
        .onClick(() => {  
          this.model.name = '老王'  
          if (!this.model.child) {  
            // 这个监听生效
            this.model.child = { age: 12, name: '小王' }  
          } else {  
            // 这个嵌套类型的监听无效  
            this.model.child.name = '小李'  
          }  
        })  
        .margin({ top: 10 })   
    }
  }  
}  
  
export interface FatherModel {  
  name: string  
  child ?: ChildModel  
}
  
export interface ChildModel {  
  age ?: number  
  name ?: string  
}

需要注意的是嵌套简单类型的数组可以触发更新(嵌套object不能触发push更新,只能触发赋值时的更新):

ts
@Component
struct QuickDevView {

  @State model: FatherModel = { name: '' }

  build() {
    Column() {
      Column() {
        if (this.model.childrenList) {
          ForEach(this.model.childrenList, (x:string) => {
            Text(x)
            ChildView({ name: x }).backgroundColor(Color.Yellow)
          })
        }
      }.backgroundColor(Color.Green)

      Button("click me")
        .onClick(() => {       
          if (!this.model.childrenList) {
            // 生效
            this.model.childrenList = ['a']
          } else {
            // 生效(push/pop生效。shift不行)
            this.model.childrenList.push('b')
          }
        })
        .margin({ top: 10 })
    }
    .width('100%')
  }
}

@Component  
struct ChildView {  
  @Prop name: string = ''  
  
  build() {  
    Text(this.name)  
  }  
}

export interface FatherModel {
  name: string
  child ?: ChildModel
  childrenList ?: string[]
}

export class ChildModel {
  age ?: number
  name ?: string
}
@Component
struct QuickDevView {

  @State model: FatherModel = { name: '' }

  build() {
    Column() {
      Column() {
        if (this.model.childrenList) {
          ForEach(this.model.childrenList, (x:string) => {
            Text(x)
            ChildView({ name: x }).backgroundColor(Color.Yellow)
          })
        }
      }.backgroundColor(Color.Green)

      Button("click me")
        .onClick(() => {       
          if (!this.model.childrenList) {
            // 生效
            this.model.childrenList = ['a']
          } else {
            // 生效(push/pop生效。shift不行)
            this.model.childrenList.push('b')
          }
        })
        .margin({ top: 10 })
    }
    .width('100%')
  }
}

@Component  
struct ChildView {  
  @Prop name: string = ''  
  
  build() {  
    Text(this.name)  
  }  
}

export interface FatherModel {
  name: string
  child ?: ChildModel
  childrenList ?: string[]
}

export class ChildModel {
  age ?: number
  name ?: string
}

注意问题

如下,增加了第三行代码后,发现嵌套类型的监听生效了。因为第三行代码触发了model更新(按官方的话说是冗余更新),造成了@status可以监听嵌套的class/object的错觉。

js
Button("button click")  
  .onClick(() => {  
    this.model.name = '' + Math.random()  
    if (!this.model.child) {  
      // 这个监听生效  
      this.model.child = { age: 12, name: '小王' }  
    } else {  
      // 这个嵌套类型的监听生效了 
      this.model.child.name = '小李' + Math.random()  
    }  
  })
Button("button click")  
  .onClick(() => {  
    this.model.name = '' + Math.random()  
    if (!this.model.child) {  
      // 这个监听生效  
      this.model.child = { age: 12, name: '小王' }  
    } else {  
      // 这个嵌套类型的监听生效了 
      this.model.child.name = '小李' + Math.random()  
    }  
  })

使用@Track装饰器可以避免冗余刷新: @Track装饰器

可监听嵌套类的数据更新。

简单模型嵌套

注意以下示例, 父view中监听不到嵌套类型的更新;

js
@Observed
class BookName {
  public nameSize: number;

  constructor(nameSize: number) {
    this.nameSize = nameSize;
  }
}

@Observed
class Book {
  public bookName: BookName;

  constructor(bookName: BookName) {
    this.bookName = bookName;
  }
}

@Component
struct ChildView {
  label: string = 'ViewC1';
  @ObjectLink bookName: BookName;

  build() {
    Row() {
      Column() {
        Text(`${this.bookName.nameSize}`)
      }
    }
  }
}

@Component
@Preview
@Entry
struct QuickDevView {
  @State child: Book = new Book(new BookName(0));

  build() {
    Column() {
      // 外部检测不到嵌套类的属性变化
      Text(this.child.bookName.nameSize + '')

      // 使用子视图结合 Observed ObjectLink 可以在子视图中触发更新
      ChildView({
        bookName: this.child.bookName
      })

      Button() {
        Text('click').padding(20)
      }.onClick(() => {
        // 类嵌套类的话,嵌套类的属性变化是检测不到的:
        this.child.bookName.nameSize += 10;
      })
    }.margin(40)
  }
}
@Observed
class BookName {
  public nameSize: number;

  constructor(nameSize: number) {
    this.nameSize = nameSize;
  }
}

@Observed
class Book {
  public bookName: BookName;

  constructor(bookName: BookName) {
    this.bookName = bookName;
  }
}

@Component
struct ChildView {
  label: string = 'ViewC1';
  @ObjectLink bookName: BookName;

  build() {
    Row() {
      Column() {
        Text(`${this.bookName.nameSize}`)
      }
    }
  }
}

@Component
@Preview
@Entry
struct QuickDevView {
  @State child: Book = new Book(new BookName(0));

  build() {
    Column() {
      // 外部检测不到嵌套类的属性变化
      Text(this.child.bookName.nameSize + '')

      // 使用子视图结合 Observed ObjectLink 可以在子视图中触发更新
      ChildView({
        bookName: this.child.bookName
      })

      Button() {
        Text('click').padding(20)
      }.onClick(() => {
        // 类嵌套类的话,嵌套类的属性变化是检测不到的:
        this.child.bookName.nameSize += 10;
      })
    }.margin(40)
  }
}

模型嵌套模型数组

需要特别注意的数组循环那层,必须提取出子组件,否则监听不到。

ts
@Component
@Preview
@Entry
struct QuickDevView {
  @State superModel: SuperModel = new SuperModel(
    new MainModel('main',
      new XTypeArray()));

  build() {
    Column() {
      Text('QuickDevView arrayModel.length: ' + this.superModel.mainModel.arrayModel.length)
      Divider()

      MainView({
        mainModel: this.superModel.mainModel
      })

      Divider()
      Row() {
        Button() {
          Text('push')
            .foregroundColor(Color.White)
            .padding(20)
        }.onClick(() => {
          this.superModel.mainModel.arrayModel.push(new XTypeWrap(true, {
            typeName: 'aaa'
          }))
        })

        Button() {
          Text('pop')
            .foregroundColor(Color.White)
            .padding(20)
        }.onClick(() => {
          if (this.superModel.mainModel.arrayModel.length) {
            this.superModel.mainModel.arrayModel.shift()
          }
        })

        Button() {
          Text('清空')
            .foregroundColor(Color.White)
            .padding(20)
        }.onClick(() => {
          this.superModel.mainModel.arrayModel = new XTypeArray();
        })

        Button() {
          Text('修改第二个元素')
            .foregroundColor(Color.White)
            .padding(20)
        }.onClick(() => {
          if (this.superModel.mainModel.arrayModel.length > 2) {
            // 这里页面不刷新
            this.superModel.mainModel.arrayModel[1].type.typeName = "续爱怒"
          }
        })
      }
    }.margin(60)
  }
}

@Component
struct Lower2View {
  @ObjectLink list: XTypeArray

  build() {
    Column() {
      ForEach(this.list, (item: XTypeWrap) => {

        Column() {
          LowerView({ model: item })
        }.borderWidth(1)
      })
    }
  }
}

@Component
struct LowerView {
  @ObjectLink model: XTypeWrap;

  build() {
    Column() {
      Text('是否展开:' + this.model.isSelected)
        .onClick(() => {
          this.model.isSelected = !this.model.isSelected
        })
      Text(this.model.type.typeName)
      Text(this.model.type.queueModel?.name ?? '空')
    }
  }
}

@Component
struct MainView {
  @ObjectLink mainModel: MainModel

  build() {
    Column() {
      Text('MainView:' + this.mainModel.arrayModel.length)
      Text('Text:' + this.mainModel.name)

      // ForEach(this.mainModel.arrayModel, (item: XTypeWrap) => {
      //   // Text(item.isSelected + 'AS')
      //   // Text(item.type.typeName)
      //   // Text(item.type.queueModel?.name ?? '空')
      //   // 先不管
      //   // reportModels ?: ListModelItem[]
      //   LowerView({ model: item })
      // })
      Lower2View({ list: this.mainModel.arrayModel })
    }
  }
}


@Observed
class SuperModel {
  public mainModel: MainModel

  constructor(mainModel: MainModel) {
    this.mainModel = mainModel
  }
}

@Observed
class MainModel {
  public name: string
  public arrayModel: XTypeArray

  constructor(name: string, arrayModel: XTypeArray) {
    this.name = name;
    this.arrayModel = arrayModel;
  }
}

@Observed
class XTypeArray extends Array<XTypeWrap> {
}

@Observed
export class XTypeWrap {
  isSelected: boolean = false
  type: InspectTypeModel

  constructor(isSelected: boolean, type: InspectTypeModel) {
    this.isSelected = isSelected
    this.type = type
  }
}

interface InspectTypeModel {
  typeName: string
  queueModel ?: NestedModel
  reportModels ?: ListModelItem[]
}

interface NestedModel {
  name: string
}

interface ListModelItem {
  name: string
}
@Component
@Preview
@Entry
struct QuickDevView {
  @State superModel: SuperModel = new SuperModel(
    new MainModel('main',
      new XTypeArray()));

  build() {
    Column() {
      Text('QuickDevView arrayModel.length: ' + this.superModel.mainModel.arrayModel.length)
      Divider()

      MainView({
        mainModel: this.superModel.mainModel
      })

      Divider()
      Row() {
        Button() {
          Text('push')
            .foregroundColor(Color.White)
            .padding(20)
        }.onClick(() => {
          this.superModel.mainModel.arrayModel.push(new XTypeWrap(true, {
            typeName: 'aaa'
          }))
        })

        Button() {
          Text('pop')
            .foregroundColor(Color.White)
            .padding(20)
        }.onClick(() => {
          if (this.superModel.mainModel.arrayModel.length) {
            this.superModel.mainModel.arrayModel.shift()
          }
        })

        Button() {
          Text('清空')
            .foregroundColor(Color.White)
            .padding(20)
        }.onClick(() => {
          this.superModel.mainModel.arrayModel = new XTypeArray();
        })

        Button() {
          Text('修改第二个元素')
            .foregroundColor(Color.White)
            .padding(20)
        }.onClick(() => {
          if (this.superModel.mainModel.arrayModel.length > 2) {
            // 这里页面不刷新
            this.superModel.mainModel.arrayModel[1].type.typeName = "续爱怒"
          }
        })
      }
    }.margin(60)
  }
}

@Component
struct Lower2View {
  @ObjectLink list: XTypeArray

  build() {
    Column() {
      ForEach(this.list, (item: XTypeWrap) => {

        Column() {
          LowerView({ model: item })
        }.borderWidth(1)
      })
    }
  }
}

@Component
struct LowerView {
  @ObjectLink model: XTypeWrap;

  build() {
    Column() {
      Text('是否展开:' + this.model.isSelected)
        .onClick(() => {
          this.model.isSelected = !this.model.isSelected
        })
      Text(this.model.type.typeName)
      Text(this.model.type.queueModel?.name ?? '空')
    }
  }
}

@Component
struct MainView {
  @ObjectLink mainModel: MainModel

  build() {
    Column() {
      Text('MainView:' + this.mainModel.arrayModel.length)
      Text('Text:' + this.mainModel.name)

      // ForEach(this.mainModel.arrayModel, (item: XTypeWrap) => {
      //   // Text(item.isSelected + 'AS')
      //   // Text(item.type.typeName)
      //   // Text(item.type.queueModel?.name ?? '空')
      //   // 先不管
      //   // reportModels ?: ListModelItem[]
      //   LowerView({ model: item })
      // })
      Lower2View({ list: this.mainModel.arrayModel })
    }
  }
}


@Observed
class SuperModel {
  public mainModel: MainModel

  constructor(mainModel: MainModel) {
    this.mainModel = mainModel
  }
}

@Observed
class MainModel {
  public name: string
  public arrayModel: XTypeArray

  constructor(name: string, arrayModel: XTypeArray) {
    this.name = name;
    this.arrayModel = arrayModel;
  }
}

@Observed
class XTypeArray extends Array<XTypeWrap> {
}

@Observed
export class XTypeWrap {
  isSelected: boolean = false
  type: InspectTypeModel

  constructor(isSelected: boolean, type: InspectTypeModel) {
    this.isSelected = isSelected
    this.type = type
  }
}

interface InspectTypeModel {
  typeName: string
  queueModel ?: NestedModel
  reportModels ?: ListModelItem[]
}

interface NestedModel {
  name: string
}

interface ListModelItem {
  name: string
}

外部修改模型数组某元素的更新

待编写。似乎使用ObservedV2Trace更好一些。