0


鸿蒙开发-HarmonyOS UI架构

初步布局Index

当我们新建一个工程之后,首先会进入Index页。我们先简单的做一个文章列表的显示

classArticle{
  title?: string
  desc?: string
  link?: string
}

@Entry
@Component
struct Index {
  @State articles: Article[]=[]build(){Row(){Scroll(){Column(){ForEach(this.articles,(item: Article)=>{Column(){Text(item.title).fontWeight(FontWeight.Bold)Text(item.desc)Text("----------")}},(item: Article)=>{return item.link
          })}.width('100%')}}.height('100%')}}

这样,我们只要把

articles

里面填充数据,就能正常显示一个列表了。

数据从哪来

可以看到上面的代码里是没有数据的,只有一个空数组。我们想要从网络获取数据。那么,数据怎么来呢?最简单粗暴的写法就是在

aboutToAppear()

中异步发送get请求,然后更新

articles

数组。

登录后复制

aboutToAppear(){// 请求网络数据
  axios.get(url).then(response=>{// 更新this.articles}}

好,现在Index界面依赖了网络库,甚至会依赖三方的axios库。在我之前一个项目中,还依赖过端云的agconnect库。于是Previewer直接报错,说因为有agconnect的依赖,Previewer编译失败。

我们可以看到Index和数据获取的逻辑强耦合在了一起。没有专注于他自身的UI布局的功能。

数据请求扔给另一个类IndexViewModel

那一堆网络请求和处理response的代码,看了就头疼。于是我们初步的设想就是把他完全丢给另一个类去处理,

IndexViewModel

@Observed // 这个不能漏,当类成员变化时可以被UI监听到exportdefaultclassIndexViewModel{
  articles?: Array<Article>refreshData(){// 请求网络数据// 更新this.articles}}

那么Index里变成了

  @State viewModel: IndexViewModel =newIndexViewModel()aboutToAppear(){this.viewModel.refreshData()}

现在Index只依赖一个

IndexViewModel

了。将来无论扩展到多少数据,统一从

IndexViewModel

里面读取。

refreshData()

里面也可以填任意多个其他的请求数据源。

可以预览了吗

我们知道,如果只布局一个固定界面,连数据都不需要,那是最简单的,预览也是没问题的。当涉及到数据的依赖,那问题就开始复杂了。Previewer的数据从哪里获得?我们知道即使现在我们把所有网络请求和数据成员都放到了

IndexViewModel

里面,但这也只是让Index界面没那么多代码,仅此而已。Index界面和

IndexViewModel

的依赖还是实实在在存在的。也就是说,Index界面还是依赖着真实的数据源,这将使未来Previewer的工作带来更多不确定性。
聪明的你一定想到了,可以写一个

IndexViewModelMock

类,和

IndexViewModel

结构一模一样,只是

refreshData()

里给

articles

赋值一个假数据。所以我们此时为了代码有条理,提取一个接口,叫

IndexViewModelInterface


这样,Index里面的成员就变成了这样

// 真机运行时
@State viewModel: IndexViewModelInterface =newIndexViewModel()// 使用Previewer时
@State viewModel: IndexViewModelInterface =newIndexViewModelMock()

现在我们又进了一步,可以用假数据预览了。但是还有手动切换数据源的操作。
哦对了,这个解决方案看似很理想,但似乎Arkts对这种结构并不支持。当

@State viewModel: IndexViewModelInterface

这样声明的成员,调用接口里的方法,会在运行时报错,说无法调用方法。

Previewer和Run的数据源隔离

现在我们做了很多重构,比最初的意大利面有条理很多。但手动切换终究还是不优雅,主要还是麻烦。我们能不能,只让UI布局做UI布局的事情,彻底把数据请求解耦。
声明一个struct

IndexContent

Index

的布局变成这样

build(){Column(){IndexContent({viewModel:this.viewModel })}}

显然Index的成员这样声明

viewModel: IndexViewModel =newIndexViewModel()

把之前所有的

Index

下的布局,放到

IndexContent

中,然后

IndexContent

的成员这样声明

@Prop viewModel: IndexViewModel

这样,

Index

里面包了一个

IndexContent

,数据的请求由

Index

控制,

IndexContent

完全被动接受数据,并进行UI布局。
运行一下,确认App可以正常运行。

那么,我们现在能预览Index了吗?不,我们只需要预览

IndexPreviewer

就行了。布局的本体现在在

IndexPreviewer

里的

IndexContent

里面。
新建一个struct

IndexPreviewer

,同样,布局里面只包含一个

IndexContent
@Preview
@Component
struct IndexPreviewer {viewModel: IndexViewModel =newIndexViewModel()asyncaboutToAppear(){// 刷新数据}build(){IndexContent({viewModel:this.viewModel })}}

稍后将重构,给

IndexPreviewer

里面提供假数据。
这样,由于main_pages.json中定义的页面路径是

"pages/Index"

,所以运行时会显示

Index

页面中的内容。预览时,不要去预览

Index

,只需要预览

IndexPreviewer

,就能快速调整布局。

分离请求和view model

还记得上文提到的

ViewModelInterface

不管用吗?

refreshData()

在接口里,运行时调用会报错。于是,我们再把

articles

refreshData()

分开,

refreshData()

放到一个新建的类

IndexModel

中。
这样,

IndexPreviewer

Index

里面依赖的成员都是

viewModel: IndexViewModel = new IndexViewModel()

,而

IndexModel

可以继承自一个抽象类(之后会解释为什么不是接口)

IndexModelBase

,再创建一个

IndexModelMock

继承自

IndexModelBase


View model中只保留状态成员的做法,参考了官方文档的 MVVM模式
至此,架构越来越明了了。
Index的完整代码如下

@Entry
@Component
struct Index {model: IndexModelInterface =newIndexModel()viewModel: IndexViewModel =newIndexViewModel()asyncaboutToAppear(){this.viewModel.articles =awaitthis.model.refreshArticles()}build(){Column(){IndexContent({viewModel:this.viewModel })}}}

IndexPreviewer的完整代码如下

@Preview
@Component
struct IndexPreviewer {model: IndexModelInterface =newIndexModelMock()viewModel: IndexViewModel =newIndexViewModel()asyncaboutToAppear(){this.viewModel.articles =awaitthis.model.refreshArticles()}build(){IndexContent({viewModel:this.viewModel })}}

聪明的你一定会想到,这两个struct代码大部分重复,为什么不提取一个基类。别问了,Arkts不支持。

@Component struct

不支持继承。

Model的实现

最终,真数据和假数据,是在Model里面区分的。
上文中,view model和model都是在界面容器(Index和IndexPreviewer)中持有的。实际上我们``能更进一步,把view model放到model里面。这样,界面容器只和model有耦合,把model里面的view model传到

IndexContent

里面
IndexModelBase的代码如下

exportdefault abstract classIndexModelBase{
  abstract refreshArticles(): Promise<Article[]>viewModel: IndexViewModel =newIndexViewModel()asyncrefreshData(){this.viewModel.articles =awaitthis.refreshArticles()}}

所以

IndexModelBase

不声明为接口,因为要持有view model,并对里面的

articles

进行更新。
接下来,让

IndexModelBase

的子类去实现具体的

refreshArticles()

方法。

IndexModel

中,通过网络请求获取数据,更新

articles

IndexModelMock

中,硬编码假数据给

articles


在上文的两个界面容器中,更新数据变得更简单。
以下是

Index

的最终完整代码

@Entry
@Component
struct Index {model: IndexModelBase

  asyncaboutToAppear(){this.model =newIndexModel()this.model.refreshData()}build(){Column(){IndexContent({viewModel:this.model.viewModel })}}}

以下是

IndexPreviewer

的最终完整代码

@Entry
@Component
struct IndexPreviewer {model: IndexModelBase

  asyncaboutToAppear(){this.model =newIndexModelMock()this.model.refreshData()}build(){Column(){IndexContent({viewModel:this.model.viewModel })}}}

7ebde29c85a3faa0ae369e2ea9dd3130.jpeg

标签: harmonyos ui 架构

本文转载自: https://blog.csdn.net/m0_62167422/article/details/136130080
版权归原作者 移不动开发技术 所有, 如有侵权,请联系我们删除。

“鸿蒙开发-HarmonyOS UI架构”的评论:

还没有评论