浅析ArkUI之@ObservedLink与@Link 原创 精华
jokerisme
发布于 2022-7-26 22:29
浏览
1收藏
前言
@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
}
©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
@ObjectLink测试代码.rar 3.17M 31次下载
赞
1
收藏 1
回复
相关推荐
@Link和@ObservedLink看了官方的解释确实有点懵,感谢楼主通过实例讲解。