“答开发者问”之HarmonyOS 鸿蒙Next技术问题解析 第4期

发布于 1周前 作者 songsunli 来自 鸿蒙OS

“答开发者问”之HarmonyOS 鸿蒙Next技术问题解析 第4期

HarmonyOS开发者小伙伴们

这里是你们强大的后盾——鸿蒙官方技术支持团队!我们深知在探索鸿蒙开发的旅途中,每一个疑问都可能是前行的绊脚石。因此,我们特别推出了“答开发者问”系列帖,定期筛选并解答大家的问题,旨在为大家答疑解惑,扫清前行路上的障碍。

我们诚挚邀请大家积极发帖,无论是技术上的疑惑,还是开发过程中的瓶颈,都可以在社区中提问交流。同时,我们也鼓励大家互帮互助,如果你有过类似问题的解决经验,不妨慷慨分享,让这份知识和力量传递给更多的开发者。你的每一个提问,每一次解答,都将为鸿蒙生态的发展贡献一份力量。

请持续关注我们的“答开发者问”系列帖,我们会定期更新内容,助开发者一臂之力。让我们携手共进,共创鸿蒙开发的辉煌未来!

本期问题如下:

1、父组件的点击事件,如何让子组件进行响应?

2、子组件的点击事件,如何正确让父组件进行响应?

3、父子组件使用@State/@Prop交互,子组件中UI成功更新,但promise中无法获取到最新的值,原因是什么?

4、同样使用@State装饰器,有时第三层数据可以更新UI,有时不可以,是什么原因?

5、@ObservedV2@Trace装饰的单例class数据,修改后为什么UI不更新?

往期问题回顾:

“答开发者问”之HarmonyOS技术问题解析 第1期-华为开发者问答 | 华为开发者联盟 (huawei.com)

“答开发者问”之HarmonyOS技术问题解析 第2期-华为开发者问答 | 华为开发者联盟 (huawei.com)

“答开发者问”之HarmonyOS技术问题解析 第3期-华为开发者问答 | 华为开发者联盟 (huawei.com)


更多关于“答开发者问”之HarmonyOS 鸿蒙Next技术问题解析 第4期的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html

6 回复

问题五:[@ObservedV2](/user/ObservedV2)和[@Trace](/user/Trace)装饰的单例class数据,修改后为什么UI不更新?

问题描述:

想要更新项目中一个单例数据源,同时刷新多个组件的UI,使用[@ObservedV2](/user/ObservedV2)和[@Trace](/user/Trace)装饰器修改单例数据后界面不会刷新,是否有其他方式实现?

深色代码主题
复制
@ObservedV2
class Arr {
  id: number = 0;
  @Trace numberArr: number[] = [];
  static shared: Arr = new Arr()
  constructor() {
    this.numberArr = [0, 1, 2];
  }
}

@Entry @ComponentV2 struct Index { arr: Arr = Arr.shared // UI不能刷新 // arr: Arr = new Arr() // UI可以刷新

build() { Column() { Text(length: <span class="hljs-subst">${<span class="hljs-variable language_">this</span>.arr.numberArr.length}</span>) .fontSize(40) Divider() ForEach(this.arr.numberArr, (item: number, index: number) => { Text(<span class="hljs-subst">${index}</span> <span class="hljs-subst">${item}</span>) .fontSize(40) })

  <span class="hljs-title class_">Button</span>(<span class="hljs-string">'push'</span>)
    .<span class="hljs-title function_">onClick</span>(<span class="hljs-function">() =&gt;</span> {
      <span class="hljs-variable language_">this</span>.<span class="hljs-property">arr</span>.<span class="hljs-property">numberArr</span>.<span class="hljs-title function_">push</span>(<span class="hljs-number">50</span>);
    })

  <span class="hljs-title class_">Button</span>(<span class="hljs-string">'pop'</span>)
    .<span class="hljs-title function_">onClick</span>(<span class="hljs-function">() =&gt;</span> {
      <span class="hljs-variable language_">this</span>.<span class="hljs-property">arr</span>.<span class="hljs-property">numberArr</span>.<span class="hljs-title function_">pop</span>();
    })

}

} }

解决方案:

原因是static修饰的静态属性属于类本身,而不是类的某个实例,框架的观察者机制通常监听实例属性的变化。
可以改用闭包单例模式,参考如下:

深色代码主题
复制
@ObservedV2
class Arr {
id: number = 0;
@Trace numberArr: number[] = [];
constructor() {
this.numberArr = [0, 1, 2];
}
}

// 创建单例实例的闭包函数 const createSingleton = (() => { let instance: Arr | null = null; return () => { if (!instance) { instance = new Arr(); } return instance; }; })();

// 获取单例实例 const sharedArr = createSingleton();

@Entry @ComponentV2 struct Index { // 获取单例实例 arr: Arr = sharedArr; // 使用闭包单例实例

build() { Column() { Text(length: <span class="hljs-subst">${<span class="hljs-variable language_">this</span>.arr.numberArr.length}</span>) .fontSize(40) Divider() ForEach(this.arr.numberArr, (item: number, index: number) => { Text(<span class="hljs-subst">${index}</span> <span class="hljs-subst">${item}</span>) .fontSize(40) })

  <span class="hljs-title class_">Button</span>(<span class="hljs-string">'push'</span>)
    .<span class="hljs-title function_">onClick</span>(<span class="hljs-function">() =&gt;</span> {
      <span class="hljs-variable language_">this</span>.<span class="hljs-property">arr</span>.<span class="hljs-property">numberArr</span>.<span class="hljs-title function_">push</span>(<span class="hljs-number">50</span>);
    })

  <span class="hljs-title class_">Button</span>(<span class="hljs-string">'pop'</span>)
    .<span class="hljs-title function_">onClick</span>(<span class="hljs-function">() =&gt;</span> {
      <span class="hljs-variable language_">this</span>.<span class="hljs-property">arr</span>.<span class="hljs-property">numberArr</span>.<span class="hljs-title function_">pop</span>();
    })

}

} }

原链接:

单例数据如何更新UI-华为开发者问答 | 华为开发者联盟 (huawei.com)

更多关于“答开发者问”之HarmonyOS 鸿蒙Next技术问题解析 第4期的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


问题四:同样使用[@State](/user/State)装饰器,有时第三层数据可以更新UI,有时不可以,是什么原因?

问题描述:

如下两段代码,逻辑基本相同,但第三层属性this.parent.son.son重新赋值后,代码1的UI更新成功,代码2的UI并未更新。只是将this.parent.son = this.son;换了位置,结果便不同了。不太明白原因,可以解答下我的疑惑吗?
代码1如下:

深色代码主题
复制
class Son {
  son: string = '000';
  constructor(son: string) {
    this.son = son;
  }
}

class Parent { son: Son = new Son(‘111’); }

@Entry @Component struct Test { @State son: Son = new Son(‘222’); @State parent: Parent = new Parent();

aboutToAppear(): void { this.parent.son = this.son; } build() { Column() { Text(<span class="hljs-subst">${<span class="hljs-variable language_">this</span>.son.son}</span>); Text(<span class="hljs-subst">${<span class="hljs-variable language_">this</span>.parent.son.son}</span>); Button(‘change’) .onClick(() => { this.parent.son.son = ‘333’; }) } } }

执行结果如下:

image.png

image.png

代码2如下:

深色代码主题
复制
class Son {
son: string = ‘000’;
constructor(son: string) {
this.son = son;
}
}

class Parent { son: Son = new Son(‘111’); }

@Entry @Component struct Test { @State son: Son = new Son(‘222’); @State parent: Parent = new Parent();

aboutToAppear(): void {

} build() { Column() { Text(<span class="hljs-subst">${<span class="hljs-variable language_">this</span>.son.son}</span>); Text(<span class="hljs-subst">${<span class="hljs-variable language_">this</span>.parent.son.son}</span>); Button(‘change’) .onClick(() => { this.parent.son = this.son; this.parent.son.son = ‘333’; }) } } }

执行结果如下:

image.png

image.png

解决方案:

@State装饰器,当装饰的数据类型为class或者Object时,可以观察到自身的赋值的变化,和其属性赋值的变化,第三层变化是无法观察的。

第一段代码中,this.parent.son = this.son是直接给parent的属性赋值,可观察;而this.parent.son.son = '333’是给第三层的属性赋值,不可观察,故UI中的this.parent.son.son不会更新。UI中this.son.son更新是因为class是引用数据类型,this.parent.son和this.son指向的是同一个堆内存中的对象,该对象的值已发生变化,框架观察到son的属性发生变化触发UI更新。

第二段代码中,this.parent.son = this.son时框架观察到parent的属性发生变化触发UI更新,这是一个异步过程,在UI更新时this.parent.son.son = '333’赋值已执行完毕,所以最终UI中的this.parent.son.son显示的是最新值。

将两次赋值分开结果就与第一段代码相同,如下:

深色代码主题
复制
struct Test {
@State son: Son = new Son(‘222’);
@State parent: Parent = new Parent();

build() { Column() { Text(<span class="hljs-subst">${<span class="hljs-variable language_">this</span>.son.son}</span>); Text(<span class="hljs-subst">${<span class="hljs-variable language_">this</span>.parent.son.son}</span>); Button(‘change’) .onClick(() => { // 点击第一个按钮执行二层属性的赋值 this.parent.son = this.son; }) Button(‘change2’) .onClick(() => { // 点击第二个按钮执行三层属性的赋值 this.parent.son.son = ‘333’; }) } } }

参考文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-state-V5#观察变化和行为表现

原链接:

@State的观测问题求解,各位大佬能解答一下小弟的疑惑吗,真的有点懵。。。-华为开发者问答 | 华为开发者联盟 (huawei.com)

问题三:父子组件使用[@State](/user/State)/[@Prop](/user/Prop)交互,子组件中UI成功更新,但promise中无法获取到最新的值,原因是什么?

问题描述:

现象如下:

  • 目前首次点击,弹出提示框文案为:【父组件初始值】,我的期望是弹出【改值了!!!】
  • 后续再进行点击没问题的,因为此时 msg 确实已经变为【改值了!!!】

排查及猜想:

  1. 首次点击与期望不符合,猜想可能有类似于 nextTick 之类的东西,应当等待子组件的 Prop 更新了之后,再去执行 showToast 。
  2. 查找文档没找到符合的 API。必须用 setTimeout 这种方式去延迟 showToast 吗?
  3. 尝试了用 setTimeout 延迟 showToast,发现只有 10ms 以上,才能达到期望结果;

请问该现象的原因是什么?怎么修改?

image.png

代码如下:

深色代码主题
复制
import { promptAction } from '@kit.ArkUI';

@Component struct Child { @Prop msg: string = “原始数据” cb: () => Promise<void> = () => { return Promise.resolve() }

build() { Button(this.msg).onClick(() => { this.cb().then(() => { console.log(“from Child”, this.msg) setTimeout(() => { promptAction.showToast({ message: this.msg }) }, 10) }) }) } }

@Entry @Component struct Parent { @State msg: string = “父组件初始值” parentFunc = () => { const pFunc: Promise<void> = new Promise((resolve: Function) => { this.msg = “改值了!!!” console.log(“from Parent”, this.msg) resolve() }) return pFunc }

build() { Column() { Child({ msg: this.msg, cb: this.parentFunc }) } } }

解决方案:

该现象是装饰器的使用问题,@State/@Prop的实现本质上是深拷贝,同步数据时不是和@State/@Link@Provide/@Consume一样同步更新。当同步父组件数据时,需要走组件更新函数,也就是会晚于父组件一帧的时间识别数据变化,进行后续处理,那么刷新的时间就需要两帧。而@State/@Link@Provide/@Consume是双向数据同步,只需要当前帧即可识别数据变化,下一帧刷新。
所以可以根据需求,使用@State/@Link@Provide/@Consume代替@State/@Prop,即可实现期望的效果。@Provide/@Consume@State/@Link的不同点在于:前者可以在多层级的父子组件之间传递。
另外还有一种方案,可以用@Watch来监听@Prop来实现。

方案一:使用@State/@Link。把原代码中@Prop改为@Link即可。
方案二:使用@Provide/@Consume。代码如下:

深色代码主题
复制
import { promptAction } from ‘@kit.ArkUI’;

@Component struct Child { @Consume(“pMsg”) msg: string cb: () => Promise<void> = () => { return Promise.resolve() }

build() { Button(this.msg).onClick(() => { this.cb().then(() => { console.log(“from Child”, this.msg) promptAction.showToast({ message: this.msg }) }) }) } }

@Entry @Component struct Parent { @Provide(“pMsg”) msg: string = “父组件初始值” parentFunc = () => { const pFunc: Promise<void> = new Promise((resolve: Function) => { this.msg = “改值了!!!” console.log(“from Parent”, this.msg) resolve() }) return pFunc }

build() { Column() { Child({ cb: this.parentFunc }) } } }

方案三:用@Watch来监听@Prop。代码如下:

深色代码主题
复制
import { promptAction } from ‘@kit.ArkUI’;

@Component struct Child { @Prop @Watch(‘chlTest’) msg: string = “原始数据” cb: () => Promise<void> = () => { return Promise.resolve() }

chlTest(propName: string): void { console.log(“from Child”, this.msg) promptAction.showToast({ message: this.msg }) }

build() { Button(this.msg).onClick(() => { this.cb().then(() => {

  })
})

} }

@Entry @Component struct Parent { @State msg: string = “父组件初始值” parentFunc = () => { const pFunc: Promise<void> = new Promise((resolve: Function) => { this.msg = “改值了!!!” console.log(“from Parent”, this.msg) resolve() }) return pFunc }

build() { Column() { Child({ msg: this.msg, cb: this.parentFunc }) } } }

原链接:

传入的 Prop 已经修改了,UI 层已经更改了,但是在 promise中无法获取到最新的值,这种应该如何处理?(急急急!)求助各位大佬!-华为开发者问答 | 华为开发者联盟 (huawei.com)

问题二:子组件的点击事件,如何正确让父组件进行响应?

问题描述:

在 web 的 vue.js 中,可以通过 emit 将子组件的事件通知给父组件,父组件@监听 emit 的 key 值就可以处理子组件的事件响应了。
ArkTS中有类似方法吗?除了官网提供的透传方式(如下图)以外,还有别的方式可以在父组件中,处理子组件内点击事件吗?

image.png

解决方案:

以下几种方案可供参考:
方案一:楼主提到的官网中的透传方案,将父组件的事件传到子组件,即可在子组件调用,在父组件响应处理。参考文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-faqs-V5/faqs-arkui-99-V5
方案二:使用事件订阅Emitter进行通信,不仅可以实现父子组件之间通信,还可以实现线程间通信。参考文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/itc-with-emitter-V5
方案三:可以在子组件中用一个[@Link](/user/Link)修饰的变量标志该事件发生,父组件中用[@State](/user/State)修饰 [@Watch](/user/Watch)监听,在监听事件中响应。参考以下demo:

深色代码主题
复制
@Entry
@Component
struct EntryComponent {
  @State @Watch('test') count: number = 7;

test() { console.log(‘testTag test in my component’ + this.count); }

build() { Column() { MyComponent({ title: ‘Hello World 2’, count: this.count }) } } }

@Component struct MyComponent { @State title: string = ‘Hello World’ @Link count: number; private increaseBy: number = 1

build() { Column() { Text(<span class="hljs-subst">${<span class="hljs-variable language_">this</span>.title}</span>).fontSize(30) Button(Click to increase count=<span class="hljs-subst">${<span class="hljs-variable language_">this</span>.count}</span>) .margin(20) .onClick(() => { // 修改变量count,触发父组件test方法 this.count += this.increaseBy }) } } }

原链接:

子组件的事件,如何正确让父组件进行响应?-华为开发者问答 | 华为开发者联盟 (huawei.com)

问题一:父组件的点击事件,如何让子组件进行响应?

问题描述:

CenterScreen是封装的自定义组件,在CenterView中引入使用。我想给CenterView的Image($r('app.media.startIcon'))设置点击事件,在CenterScreen中响应并处理逻辑,该怎样处理呢?

关键代码如下:

深色代码主题
复制
import CenterScreen from "./CenterScreen"

@Component export struct CenterView { build() { Column({space: 8}) { CenterScreen() .layoutWeight(0.95) .height(‘100%’) .padding({ left: 12, top: 12, bottom: 12 })

 ......

 <span class="hljs-title class_">Image</span>($r(<span class="hljs-string">'app.media.startIcon'</span>))
   .<span class="hljs-title function_">width</span>(<span class="hljs-number">100</span>)
   .<span class="hljs-title function_">height</span>(<span class="hljs-number">100</span>)
   .<span class="hljs-title function_">draggable</span>(<span class="hljs-literal">true</span>)
   .<span class="hljs-title function_">onClick</span>(<span class="hljs-function">() =&gt;</span> {
     <span class="hljs-comment">//  需要调用在子组件CenterScreen中定义的方法</span>
   })

}
.<span class="hljs-title function_">width</span>(<span class="hljs-string">'80%'</span>)
.<span class="hljs-title function_">height</span>(<span class="hljs-string">'100%'</span>)
.<span class="hljs-title function_">flexGrow</span>(<span class="hljs-number">1</span>)
.<span class="hljs-title function_">padding</span>({
  <span class="hljs-attr">left</span>: <span class="hljs-number">8</span>,
  <span class="hljs-attr">bottom</span>: <span class="hljs-number">8</span>
})

} }

解决方案:

 方案一:可以定义一个controller类,在controller类中定义和子组件中类型相同的方法,在子组件中将实际封装的方法给到controller。父组件在使用时,new一个controller对象传入子类中,在父组件中调用controller对应的方法。参考demo:

深色代码主题
复制
@Component
struct Child  {
@State private text: string = ‘初始值’
private controller: ChildController = new ChildController();

aboutToAppear() { if(this.controller) { //给controller对应的方法赋值 this.controller.changeText = this.changeText } }

//封装的能力 private changeText = (value: string) =>{ this.text = value }

build() { Column() { Text(this.text) } } }

//定义controller对象 class ChildController { changeText = (value: string) => {} }

@Entry @Component struct Parent { private ChildRef = new ChildController() build() { Column() { Text(‘调用Child的changeText’).fontSize(‘18vp’).fontColor(Color.Gray) Divider() Child({ controller:this. ChildRef }) Button(‘Parent调用childer的changeText’).onClick(() => { this.ChildRef.changeText(‘Parent调用childer的changeText’) }) } .justifyContent(FlexAlign.Center) .width(“100%”) .height(“100%”) } }

方案二:可以使用事件订阅实现事件传递,HarmonyOS提供了Emitter和EventHub两种方式。

  • 两者本质上都是通过发布订阅方式来实现事件的传递。
  • 使用场景有所不同,主线程内通信用EventHub,主线程与worker间通信用Emitter。
  • 使用方式有所不同,EventHub仅Stage模型可用,通过context直接获取,作用范围与使用的context等价。Emitter在FA与Stage模型都可以用,直接搭配worker就可以。
  1. Emitter初衷是用于线程间通信,只是使用方法采用事件的发布与订阅。团队开发若都需要使用到Emitter,建议将Emitter的ID进行分段,不同的团队使用不同的ID分段。
  2. 如果是在单个模块范围内进行事件发布与订阅的功能,可以考虑使用EventHub的功能,它通过context进行事件的发布和订阅。使用示例可参考:使用eventhub进行数据通信

Emitter参考文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/js-apis-emitter-V5
EventHub参考文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/js-apis-inner-application-eventhub-V5

原链接:

组件的事件传递-华为开发者问答 | 华为开发者联盟 (huawei.com)

“答开发者问”之HarmonyOS 鸿蒙Next技术问题解析 第4期

Q:鸿蒙Next系统是否支持多设备协同工作的无缝切换?

A:是的,鸿蒙Next系统原生支持多设备协同工作,能够实现设备间的无缝切换。通过分布式技术,系统能够智能识别并连接用户身边的鸿蒙设备,实现跨设备的资源共享和协同工作,如跨屏显示、文件快速传输等,提升用户体验。

Q:鸿蒙Next系统对于应用生态的兼容性和拓展性如何?

A:鸿蒙Next系统在设计上充分考虑了应用生态的兼容性和拓展性。系统支持多种应用框架,能够兼容Android应用,并提供了丰富的API接口,方便开发者进行应用开发和移植。同时,鸿蒙Next系统还支持分布式应用,能够实现跨设备的应用协同和资源共享。

Q:鸿蒙Next系统在安全性方面有哪些提升?

A:鸿蒙Next系统在安全性方面进行了全面升级。系统采用了多重安全防护机制,包括设备级安全、应用级安全和数据级安全等,能够全方位保障用户数据和隐私的安全。同时,鸿蒙Next系统还支持安全支付、安全通信等功能,提升用户在使用过程中的安全性。

如果问题依旧没法解决请联系官网客服,官网地址是:https://www.itying.com/category-93-b0.html。该地址提供了详细的联系方式和在线客服支持,能够帮助您解决关于鸿蒙Next系统的相关问题。

回到顶部