1. @Observed&@ObjectLink

作用: 用于嵌套对象类对象属性的响应式更新(修改多层数据嵌套的值、可以引起UI更新)

v1版本中修改多层数据的嵌套,无法引起UI更新

要么直接修改整个对象的第一层数据,要么用@Observed&@ObjectLink解决数据更新问题

@Observed: 用于装饰类,表示这个类的实例可以被观察,当类属性变化时会触发UI更新

@ObjectLink: 用于装饰组件中的变量,表示这个变量与父组件中的@Observed对象建立双向绑定

注意事项

  1. @Observed必须修饰类,不能直接修饰基本类型或数组
  2. @ObjectLink只能用于被@Observed修饰的类的实例
  3. @ObjectLink不能给初始值—>不然报错,解决:让父组件传递数据给初始值

案例的改写:解决数据多层嵌套修改值时,无法引起UI更新

之前是修改第一层数据的整条对象,性能消耗过大,且先销毁在创建导致头像闪烁

核心步骤:

  • interface 改为 类 class 并使用 @Observed 修饰组件中的变量
  • 通过 new 的方式完成数据的创建
  • 状态修饰符改为 @ObjectLink(双向数据同步)在子组件修改数据:直接修改
// 数据接口
interface IReplyItem {
  id: number
  avatar: ResourceStr
  author: string
  content: string
  time: string
  area: string
  likeNum: number
  likeFlag: boolean
}
@Observed
class  ReplyItem{
  id: number
  avatar: ResourceStr
  author: string
  content: string
  time: string
  area: string
  likeNum: number
  likeFlag: boolean
  constructor(value:IReplyItem) {
    this.id = value.id
    this.avatar =value. avatar
    this.author = value.author
    this.content = value.content
    this.time = value.time
    this.area =value. area
    this.likeNum = value.likeNum
    this.likeFlag = value.likeFlag
  }
}
class ReplyData {
  static getCommentList(): ReplyItem[] {
    return [
      new ReplyItem( {
       id: 1,
       avatar: 'https://picx.zhimg.com/027729d02bdf060e24973c3726fea9da_l.jpg?source=06d4cd63',
       author: '偏执狂-妄想家',
       content: '更何况还分到一个摩洛哥[惊喜]',
       time: '11-30',
       area: '海南',
       likeNum: 34,
       likeFlag: false
     }),
      new ReplyItem({
          id: 2,
          avatar: 'https://pic1.zhimg.com/v2-5a3f5190369ae59c12bee33abfe0c5cc_xl.jpg?source=32738c0c',
          author: 'William',
          content: '当年希腊可是把1:0发挥到极致了',
          time: '11-29',
          area: '北京',
          likeNum: 58,
          likeFlag: true
        }),
      new ReplyItem({
          id: 3,
          avatar: 'https://picx.zhimg.com/v2-e6f4605c16e4378572a96dad7eaaf2b0_l.jpg?source=06d4cd63',
          author: 'Andy Garcia',
          content: '欧洲杯其实16队球队打正赛已经差不多,24队打正赛意味着正赛阶段在小组赛一样有弱队。',
          time: '11-28',
          area: '上海',
          likeNum: 10,
          likeFlag: false
        }),
      new ReplyItem({
          id: 4,
          avatar: 'https://picx.zhimg.com/v2-53e7cf84228e26f419d924c2bf8d5d70_l.jpg?source=06d4cd63',
          author: '正宗好鱼头',
          content: '确实眼红啊,亚洲就没这种球队,让中国队刷',
          time: '11-27',
          area: '香港',
          likeNum: 139,
          likeFlag: true
        }),
      new ReplyItem({
          id: 5,
          avatar: 'https://pic1.zhimg.com/v2-eeddfaae049df2a407ff37540894c8ce_l.jpg?source=06d4cd63',
          author: '柱子哥',
          content: '我是支持扩大的,亚洲杯欧洲杯扩到32队,世界杯扩到64队才是好的,世界上有超过200支队伍,欧洲区55支队伍,亚洲区47支队伍,即使如此也就六成出现率',
          time: '11-27',
          area: '旧金山',
          likeNum: 29,
          likeFlag: false
        }) ,
      new ReplyItem({
          id: 6,
          avatar: 'https://picx.zhimg.com/v2-fab3da929232ae911e92bf8137d11f3a_l.jpg?source=06d4cd63',
          author: '飞轩逸',
          content: '禁止欧洲杯扩军之前,应该先禁止世界杯扩军,或者至少把亚洲名额一半给欧洲。',
          time: '11-26',
          area: '里约',
          likeNum: 100,
          likeFlag: false
        })
    ]
  }
  static getRootComment(): ReplyItem {
    return  new ReplyItem({
      id: 1,
      avatar: $r("app.media.background"),
      author: '周杰伦',
      content: '意大利拌面应该使用42号钢筋混凝土再加上量子力学缠绕最后通过不畏浮云遮望眼',
      time: '11-30',
      area: '海南',
      likeNum: 98,
      likeFlag: true
    })
  }
}

@Component
  // 评论组件
struct replyComment {
  // 评论数据由外部传入,会更改点赞个数,所以通过@Prop 定义
  // @Prop reply: Partial<ReplyItem> = {}
  @ObjectLink reply: ReplyItem//不用@Prop单项接受数据了,改成@ObjectLink(可双向数据同步)
  // 切换点赞 ,逻辑由外部传入,这里定义为空的箭头函数即可
  changeLike = () => {}
  build() {
    Row() {
      Image(this.reply.avatar)// 头像
        .width(32)
        .height(32)
        .borderRadius(16)
      Column({ space: 10 }) {
        Text(this.reply.author)// 作者
          .fontWeight(600)
        Text(this.reply.content)// 内容
          .lineHeight(20)
          .fontSize(14)
          .fontColor("#565656")

        Row() {
          Text(`${this.reply.time} . IP属地 ${this.reply.area}`)// 时间+区域
            .fontColor("#c3c4c5")
            .fontSize(12)
          Row() {
            Image($r("sys.media.AI_search"))
              .width(14)
              .aspectRatio(1)// 是否点赞
              .fillColor(this.reply.likeFlag ? Color.Red : "#c3c4c5") //  "#c3c4c5" 或 red

            Text(this.reply.likeNum?.toString())// 点赞数
              .fontSize(12)
              .margin({left: 5 })
          }// 点击 心和数组均会触发这个效果,所以点击事件绑定给 Row
          .onClick(() => {this.changeLike() })
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceBetween)
      }
      .alignItems(HorizontalAlign.Start)
      .layoutWeight(1)
      .padding({
        left: 15,
        right: 5
      })
    }
    .justifyContent(FlexAlign.Start)
    .alignItems(VerticalAlign.Top)
    .width('100%')
    .padding(15)
  }
}

// 底部发布评论组件
@Component
struct addReply {
  @State iptString: string = ''
  // 点击发布评论时执行的方法
  // 参数 1:需要传递给父组件的 输入值
  clickPublish = (reply: string) => {

  }

  build() {
    Row() {
      TextInput({ placeholder: '回复', text: $$this.iptString })
        .layoutWeight(1)
        .backgroundColor("#f4f5f6")
        .height(40)
      Text('发布')
        .fontColor("#6ecff0")
        .fontSize(14)
        .margin({
          left: 10
        })
        .onClick(() => {
          this.clickPublish(this.iptString)
        })

    }
    .padding(10)
    .backgroundColor(Color.White)
    .border({
      width: { top: 1 },
      color: "#f4f5f6"
    })
  }
}

@Entry
@Component
struct Page02_zhihu {
  // 评论列表 数组
  @State commentList: ReplyItem[] = ReplyData.getCommentList()
  // 顶部评论 对象
  @State rootComment: ReplyItem = ReplyData.getRootComment()

  build() {
    Column() {
      // 上半部分
      Column() {
        Column() {
          // 顶部区域
          Row() {
            Row() {
              Image($r('sys.media.ohos_ic_public_arrow_left'))
                .width(20)
                .height(20)
            }
            .borderRadius(20)
            .backgroundColor('#f6f6f6')
            .justifyContent(FlexAlign.Center)
            .width(30)
            .aspectRatio(1)
            .margin({
              left: 15
            })

            Text("评论回复")
              .layoutWeight(1)
              .textAlign(TextAlign.Center)
              .padding({
                right: 35
              })
          }
          .width('100%')
          .height(50)
          .border({
            width: {
              bottom: 1
            },
            color: '#f6f6f6',
          })


          // 顶部评论
          replyComment({
            reply: this.rootComment, changeLike: () => {
              // 修改状态
              this.rootComment.likeFlag = !this.rootComment.likeFlag
              // 根据点赞的状态 修改个数
              if (this.rootComment.likeFlag == true) {
                this.rootComment.likeNum++
              } else {
                this.rootComment.likeNum--
              }
            }
          })

          // 分割线
          Divider()
            .strokeWidth(6)
            .color("#f4f5f6")

          // 回复数
          Text() {
            Span('回复')
            Span(`${this.commentList.length}`)
          }
          .padding(15)
          .fontWeight(700)
          .alignSelf(ItemAlign.Start)

          // 回复评论列表
          List() {
            ForEach(this.commentList, (item: ReplyItem, index: number) => {
              ListItem() {

                replyComment({
                  reply: item, changeLike: () => {
                    // 修改状态
                    item.likeFlag = !item.likeFlag
                    // 修改个数
                    if (item.likeFlag == true) {
                      item.likeNum++
                    } else {
                      item.likeNum--
                    }
                    // 上述代码执行完毕之后,数据已经改变了,但是页面不更新,因为是深层属性
                    // 通过 splice 方法触发更新(页面会闪,明天讲解决方案)
                    // this.commentList.splice(index, 1, item)

                  }
                })
              }
            })
          }
          .layoutWeight(1)
          .scrollBar(BarState.Off)
          .divider({ strokeWidth: 1, startMargin: 40, endMargin: 10 })

        }
        .width('100%')
        .backgroundColor(Color.White)
      }
      .layoutWeight(1)

      // 底部的发布评论区域
      addReply({
        clickPublish: (reply: string) => {
          this.commentList.unshift(new ReplyItem({//改成new,因为数据类型改成了class
            id: Date.now(),
            avatar: $r('app.media.background'),
            author: '小黑',
            content: reply,
            time: `${new Date().getMonth() + 1}-${new Date().getDate()}`,
            area: '北京',
            likeNum: 0,
            likeFlag: false
          }))
        }
      })
    }
    .height('100%')
  }
}



2. @Link双向同步

作用: 与父组件建立双向数据同步

注意事项:

  1. @Link 不能本地初始化, 必须从父组件进行初始化
  2. @Link 不能接收ForEach中对象数组数据

使用步骤:

  1. 将父组件的状态属性传递给子组件
  2. 子组件通过@Link修饰即可
  3. 简单数据和复杂数据的更新都能检测到,但复杂数据的值依然只能检测到第一层的变化,如果多层嵌套修改,用@Observed&@ObjectLink
interface LinKDemo {
  name?: string
  age: number
}

@Entry // 父组件
@Component
struct KnowledgePageF {
  //必须加@state或者其他装饰器
  // (因为子组件用@link接收的数据,有可能会双向数据更新)
  @State count: number = 0
  @State obj: LinKDemo = { age: 1 }
  build() {
    Column() {
      Text('父组件-简单数据: ' + this.count)
        .fontSize(30)
      Text('父组件-复杂数据: ' + this.obj.age)
        .fontSize(30)
        .margin({bottom:20})
      Button('修改数据')
        .onClick(() => {
          this.count++
          this.obj.age++
        })
      SonsComponent({
        sonDemo: this.count,
        sonObj: this.obj
      })
    }
    .padding(10)
    .height('100%')
    .backgroundColor('#ccc')
    .width('100%')
    .alignItems(HorizontalAlign.Center)
    .padding({ top: 100 })
  }
}


@Component  // 子组件
struct SonsComponent {
  //Link不能初始化  (让父组件去初始化)
  @Link sonDemo: number
  @Link sonObj: LinKDemo

  build() {
    Column({ space: 20 }) {
      Text('子组件-简单数据: ' + this.sonDemo)
        .fontSize(20)
      Text('子组件-复杂数据: ' + this.sonObj.age)
        .fontSize(20)
      Button('修改数据')
        .onClick(() => {
          this.sonDemo++
          this.sonObj.age++
        })
    }
    .backgroundColor(Color.Pink)
    .alignItems(HorizontalAlign.Center)
    .width('80%')
    .margin({ top: 100 })
    .padding(10)
    .borderRadius(10)

  }
}
@Entry//父组件
@Component
struct Day09_LinkForEach {
  build() {
    Column() {
      ForEach(Array.from({ length: 5 }), (item: number) => {
        SonLinkForEach({ item: item })
      })
    }
    .height('100%').width('100%')
  }
}
@Component//子组件
struct SonLinkForEach {
  //1.@Link双向数据同步
  //注意点: 1.不能自己初始化值(让父组件初始化)、2.不能通过ForeEach循环传递数据
  // @Link item:number
  @Prop item: number//使用@prop接收-不报错
  build() {
    Column() {
      Text('111')
    }
  }
}


3. @Provide、@Consume跨组件传值

作用: 用于跨层级组件(祖先与后代)之间的数据同步

注意事项:

  1. @Consume 不能本地初始化, 从父组件进行初始化
  2. 通过相同的变量名绑定,也可以修改变量名——>@Consume('变量名') 更改的名字: 类型
// 写法 1:通过相同的变量名绑定
@Provide a: number = 0;
@Consume a: number;

// 写法 2通过相同的变量别名绑定
@Provide b: number = 0;
@Consume('b') c: number;

使用步骤:

  1. 将父组件的状态属性使用@Provide修饰
  2. 子组件通过@Consume修饰
interface objS{
  age:number
}
@Entry  // 顶级组件
@Component
struct RootComponent {
  @Provide count:number=0
  @Provide obj: objS={age:10}
  build() {
    Column() {
      Text('顶级组件 '+this.count+'-'+this.obj.age)
        .fontSize(30)
        .fontWeight(900)
      ParentComponent()//二级组件
    }
    .padding(10)
    .height('100%')
    .backgroundColor('#ccc')
    .width('100%')
    .alignItems(HorizontalAlign.Center)
    .padding({ top: 100 })
  }
}


@Component
  // 二级组件
struct ParentComponent {
  @Consume count:number
  @Consume obj:objS
  build() {
    Column({ space: 20 }) {
      Text('我是二级组件'+this.count+'-'+this.obj.age)
        .fontSize(22)
        .fontWeight(900)
      SonComponentDemo()//三级组件
    }
    .backgroundColor('#a6c398')
    .alignItems(HorizontalAlign.Center)
    .width('90%')
    .margin({ top: 50 })
    .padding(10)
    .borderRadius(10)

  }
}
@Component
  // 内层组件
struct SonComponentDemo {
  //Consume不能初始化
  @Consume count:number
  //复杂数据类型也可以双向传递(多层嵌套不行,只能更新第一层)
  @Consume obj:objS
  build() {
    Column({ space: 20 }) {
      Text('我是内层组件'+this.count+'-'+this.obj.age)
        .fontSize(20)
        .fontWeight(900)
       .onClick(()=>{
        this.count++
         this.obj.age++
        })
    }
    .backgroundColor('#bf94e4')
    .alignItems(HorizontalAlign.Center)
    .width('90%')
    .margin({ top: 50 })
    .padding(10)
    .borderRadius(10)

  }
}

跨组件传值注意事项

4. @Watch-状态监听器

作用: 监听某个状态变量的值是否改变

注意事项:

  1. @Watch无法单独使用,必须配合状态装饰器,比如@State、@Prop、@Link(@watch写在装饰器后面,变量的前面)
  2. 当状态变量值发生更改会调用此方法
import { promptAction } from '@kit.ArkUI'
@Entry  // 父组件
@Component
struct KnowledgePage {
  // @Watch
/*  1.不能单独使用需要和其他装饰器配合2.当状态变量值发生更改会调用此方法*/
  @State
  @Watch('changCount')
  countWatch: number = 0
  // 方法 当状态变量值发生更改会调用此方法
  changCount(){
       promptAction.openToast({
               message: '数据变化了',
               bottom: ''
             })
  }
  build() {
    Column() {
      Text('父组件')
        .fontSize(30)
      Text(this.countWatch+'')
      Button('修改数据')
        .onClick(() => { 
          this.countWatch += 2 
        })
      SonComponent({ countWatch: this.countWatch })
    }
    .padding(10)
    .height('100%')
    .backgroundColor('#ccc')
    .width('100%')
    .alignItems(HorizontalAlign.Center)
    .padding({ top: 100 })
  }
}
@Component
  // 子组件
struct SonComponent {
  @Link countWatch: number
  // 编写 UI
  build() {
    Column({ space: 20 }) {
      Text('我是子组件')
        .fontSize(20)
      Text(this.countWatch+'')
      Column() {
        Button('修改countWatch')
          .onClick(() => {
            this.countWatch++
          })
      }
    }
    .backgroundColor('#a6c398')
    .alignItems(HorizontalAlign.Center)
    .width('80%')
    .margin({ top: 30 })
    .padding(10)
    .borderRadius(10)

  }
}

5. @BuilderParam自定义结构内容

@BuilderParam——类比前端插槽slot

在一个组件中定义另一个组件,叫做尾随闭包

使用BuilderParam的步骤

  1. 需要出现父子组件的关系
  2. @BuilderParam应出现在子组件中
  3. @BuilderParam的类型是一个箭头函数
  4. BuilderParam的参数可给初始值 也可以不给初始值

如果给了初始值, 就是没有内容的默认内容,@BuilderParam 初始值必须是Builder修饰的

  1. 父组件传入的时候,传入的是用@Builder书写的结构

避免this指向问题,在父组件向子组件传递@Builder结构时,用箭头函数包裹@Builder结构

5.1. BuilderParam骨架屏

6. 状态通讯小结:

  1. @State: 修饰之后,引起UI更新
  2. @Prop: 父子组件单向通信(@prop可以初始化-也可以不初始化,不初始化时默认是underfind)
  3. @Observed&@ObjectLink: 用于嵌套对象或类对象属性的响应式更新
  4. @Link: 父子双向通信(@link不能初始化)
  5. @Provide&@Consume: 跨组件双向通信(@Consume不能初始化)
  6. @Watch: 监视数据,数据改变 -> 执行方法
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐