浅析ArkUI之@ObservedLink与@Link 原创 精华

jokerisme
发布于 2022-7-26 22:29
浏览
1收藏

本文正在参加星光计划3.0–夏日挑战赛

前言

@Link和@ObservedLink都是用来双向同步父组件和子组件的状态,那它们有什么区别呢?本文做一些浅析比较他们的使用场景和区别以及使用@ObservedLink注意事项,起到抛砖引玉的效果

@Link与@ObservedLink的使用场景

@Link

@Link往往与@State搭配使用,前者在子组件,后者在父组件

@Entry
@Component
struct Player {
    @State isPlaying: boolean = false
    build() {
        Column() {
            PlayButton({buttonPlaying: $isPlaying})
            Text(`Player is ${this.isPlaying? '':'not'} playing`)
        }
    }
}

@Component
struct PlayButton {
    @Link buttonPlaying: boolean
    build() {
        Column() {
            Button() {
                Image(this.buttonPlaying? 'play.png' : 'pause.png')
            }.onClick(() => {
                this.buttonPlaying = !this.buttonPlaying
            })
        }
    }
}
  • 解释:Player作为父组件持有@State变量isPlaying,PlayButton作为子组件持有@Link的变量buttonPlaying,两个变量指向同一个对象。@Link修饰的变量更像C语言的指针

@ObservedLink

@Link和@ObservedLink很类似,都可以理解成一个指针,子组件的对象指向父组件的对象,但是什么时候用@Link,什么时候用@ObservedLink呢?

官方解释给出了一大段说明引入的动机,我看的比较懵逼,有理解的小伙伴不妨评论解释下。按照我的理解是单个变量用@Link,一组变量用@ObservedLink

注:@Link也可以修饰自定义的class

@Link针对于单个变量

class CircleBean {
  radius: number
  offsetX: number = 0
  offsetY: number = 0
}


@Entry
@Component
struct Index {
  @State bean: CircleBean = new CircleBean()
  
   build() {
    Stack() {
      MyCircle({ bean: $bean, onTouch: this.onBeanTouch.bind(this) })
    }
    .width('100%')
    .height('100%')
  }
}

@Component
struct MyCircle {
  @Link bean: CircleBean

  build() {
    Circle()
      .size({ width: this.bean.radius, height: this.bean.radius })
      .offset({ x: this.bean.offsetX, y: this.bean.offsetY })
      .backgroundColor("#ff0")
  }
}
  • 解释:
    1. 如上我们定义了一个CircleBean类,保存圆的信息
    2. Index作为父组件定义了@State变量bean
    3. MyCircle作为子组件定义了@Link变量bean,指向父组件的@State变量

@ObservedLink用来渲染一组对象

如果是用到Foreach的情况下就需要用到@ObservedLink,因为@Link需要一个@State的变量来连接,而我们需要渲染一堆组件,往往把这一对数据放到一个集合中,然后使用ForEach,这时就不能使用@Link而用@ObservedLink了

@Observed
class CircleBean {
  radius: number = 0
  offsetX: number = 0
  offsetY: number = 0
}

@Entry
@Component
struct Index {
  @State beans: Array<CircleBean> = new Array<CircleBean>()
  
  onPageShow() {
    let offset = 0
    for (let i = 0;i < 3; i++) {
      let b = new CircleBean()
      b.radius = (i + 1) * 50
      b.offsetX = 0
      offset += b.radius
      b.offsetY = offset
      this.beans.push(b)
    }
  }
  
   build() {
    Stack() {
      ForEach(this.beans, (b) => {
        MyCircle({ bean: b, onTouch: this.onBeanTouch.bind(this) })
      })
    }
    .width('100%')
    .height('100%')
  }
}

@Component
struct MyCircle {
  @ObjectLink bean: CircleBean

  build() {
    Circle()
      .size({ width: this.bean.radius, height: this.bean.radius })
      .offset({ x: this.bean.offsetX, y: this.bean.offsetY })
      .backgroundColor("#ff0")
  }
}
  • 解释

    1. 在上面的基础上我们使用的@Observed来修饰CircleBean
    2. 在父组件Index中我们定义了@State的集合变量beans
    3. 在父组件Index的build中我们使用ForEach来创建MyCircle组件
    4. 在MyCircle中我们使用ObjectLink来接收,有人会说这里换成@Link行不行,不行,因为ForEach哪里参数b是局部变量,而不是@State修饰的成员变量
  • 总结:@Link适用于单个对象,@ObservedLinked适用于集合对象

@ObservedLinked的副作用

子类继承被@Observed过的父类不能再添加@Observed

@Observed
class CircleBean {
  radius: number
}

@Observed
class RingBean extends CircleBean {
  thickness: number
}

浅析ArkUI之@ObservedLink与@Link-鸿蒙开发者社区

  • 解释:如上,CircleBean被@Observed修饰后,那么其子类RingBean不能再被@Observed修饰了,否则抛出如上异常

被observed修饰的类的不能使用instanceof判断对象类型

@Observed
class CircleBean {
  radius: number
}

class RingBean extends CircleBean {
  thickness: number
}

let b = new CircleBean()
console.log("b instanceof CircleBean:"+(b instanceof CircleBean))		//false
b = new RingBean()
console.log("b instanceof CircleBean:"+(b instanceof CircleBean))		//false
console.log("b instanceof CircleBean:"+(b instanceof RingBean))			//false
  • 解释:CircleBean被@Observed修饰后,不能通过instanceof来判断对象b的类型,如上打印都是false

被observed修饰的类的对象不支持继承

@Observed
class CircleBean {
  radius: number
  
  fun1(){
  	console.log("fun1")
  }
}

class RingBean extends CircleBean {
  thickness: number
  
  fun2(){
  	console.log("fun2")
  }
}

let b = new CircleBean()
b.fun1()
let rb: RingBean = new RingBean()
rb.fun2()

浅析ArkUI之@ObservedLink与@Link-鸿蒙开发者社区

  • 解释:上诉代码虽然编译成功,但是运行时会第20行代码抛出异常,找不到fun2()这个方法

静态方法不能使用

@Observed
class CircleBean {
  radius: number

  static print() {
    console.log('print')
  }
}

@Entry
@Component
struct Index {
    onPageShow() {
        RingBean.print()
    }
}

浅析ArkUI之@ObservedLink与@Link-鸿蒙开发者社区

  • 分析:在上面的14行,调用print方法就会抛出一个异常TypeError: not a function

总结:Observed会影响类的继承,类的静态方法

状态变量不要通过传递参数方式修改

  • 不要通过传参的方式去改状态变量
@Component
struct MyCircle {
	@ObjectLink bean: CircleBean
    build() {
        Circle()
          .size({ width: this.bean.radius, height: this.bean.radius })
          .onTouch(e => {
  			 this.onBeanTouch(this.bean, e)
             this.onBeanTouch2(e)
          })
          .offset({ x: this.bean.offsetX, y: this.bean.offsetY })
          .backgroundColor("#ff0")
      }


    onBeanTouch(b: CircleBean, e: TouchEvent) {
        console.log("b==this.bean:"+(b==this.bean)+",b====this.bean:"+(b===this.bean))
        console.log("onBeanTouch:" + b.offsetX + "," + b.offsetY + ",type:" + e.type)
        switch (e.type) {
          case TouchType.Down: {
            this.screenX = e.touches[0].screenX
            this.screenY = e.touches[0].screenY
            break
          }

          case TouchType.Move: {
            let x = e.touches[0].screenX - this.screenX
            let y = e.touches[0].screenY - this.screenY
            b.offsetX += x;
            b.offsetY += y;
            break
          }
          case TouchType.Up: {
            let x = e.touches[0].screenX - this.screenX
            let y = e.touches[0].screenY - this.screenY
            b.offsetX += x;
            b.offsetY += y;
            break
          }
        }
        this.screenX = e.touches[0].screenX
        this.screenY = e.touches[0].screenY
      }

解释:Line8调用onBeanTouch时将this.bean作为形参传递过去,并在onBeanTouch中用b来代替,经过测试对b的内容进行修改不会引起重绘,经过打印比较b和this.bean,无论比较内容还是比较地址都是false,我们可以得出函数调用会改变我们的对象

  • 可以使用局部变量来引用状态变量,对局部变量的修改会引起重绘
onBeanTouch2(e: TouchEvent) {
    let b = this.bean
    console.log("b==this.bean:"+(b==this.bean)+",b====this.bean:"+(b===this.bean))
    console.log("onBeanTouch:" + b.offsetX + "," + b.offsetY + ",type:" + e.type)
    switch (e.type) {
      case TouchType.Down: {
        this.screenX = e.touches[0].screenX
        this.screenY = e.touches[0].screenY
        break
      }

      case TouchType.Move: {
        let x = e.touches[0].screenX - this.screenX
        let y = e.touches[0].screenY - this.screenY
        b.offsetX += x;
        b.offsetY += y;
        break
      }
      case TouchType.Up: {
        let x = e.touches[0].screenX - this.screenX
        let y = e.touches[0].screenY - this.screenY
        b.offsetX += x;
        b.offsetY += y;
        break
      }
    }
    this.screenX = e.touches[0].screenX
    this.screenY = e.touches[0].screenY
  }

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
@ObjectLink测试代码.rar 3.17M 29次下载
1
收藏 1
回复
举报
1条回复
按时间正序
/
按时间倒序
红叶亦知秋
红叶亦知秋

@Link和@ObservedLink看了官方的解释确实有点懵,感谢楼主通过实例讲解。

回复
2022-7-27 10:25:21
回复
    相关推荐