本文正在參加星光計劃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")
}
}
- 解釋:
- 如上我們定義了一個CircleBean類,儲存圓的資訊
- Index作為父元件定義了@State變量bean
- 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")
}
}
- 解釋
- 在上面的基礎上我們使用的@Observed來修飾CircleBean
- 在父元件Index中我們定義了@State的集合變量beans
- 在父元件Index的build中我們使用ForEach來建立MyCircle元件
- 在MyCircle中我們使用ObjectLink來接收,有人會說這裡換成@Link行不行,不行,因為ForEach哪裡參數b是局部變量,而不是@State修飾的成員變量
- 總結:@Link适用于單個對象,@ObservedLinked适用于集合對象
@ObservedLinked的副作用
子類繼承被@Observed過的父類不能再添加@Observed
@Observed
class CircleBean {
radius: number
}
@Observed
class RingBean extends CircleBean {
thickness: number
}
- 解釋:如上,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()
- 解釋:上訴代碼雖然編譯成功,但是運作時會第20行代碼抛出異常,找不到fun2()這個方法
靜态方法不能使用
@Observed
class CircleBean {
radius: number
static print() {
console.log('print')
}
}
@Entry
@Component
struct Index {
onPageShow() {
RingBean.print()
}
}
- 分析:在上面的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