天天看點

淺析ArkUI之@ObservedLink與@Link前言@Link與@ObservedLink的使用場景@ObservedLinked的副作用狀态變量不要通過傳遞參數方式修改

本文正在參加星光計劃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前言@Link與@ObservedLink的使用場景@ObservedLinked的副作用狀态變量不要通過傳遞參數方式修改
  • 解釋:如上,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前言@Link與@ObservedLink的使用場景@ObservedLinked的副作用狀态變量不要通過傳遞參數方式修改
  • 解釋:上訴代碼雖然編譯成功,但是運作時會第20行代碼抛出異常,找不到fun2()這個方法

靜态方法不能使用

@Observed
class CircleBean {
  radius: number

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

@Entry
@Component
struct Index {
    onPageShow() {
        RingBean.print()
    }
}
           
淺析ArkUI之@ObservedLink與@Link前言@Link與@ObservedLink的使用場景@ObservedLinked的副作用狀态變量不要通過傳遞參數方式修改
  • 分析:在上面的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
  }
           

附件連結:https://ost.51cto.com/resource/2224

繼續閱讀