鸿蒙学习笔记二十五:开发框架的经验总结

1:多层级组件的传值问题

当项目中存在祖父组件、父组件、子组件 或者 父组件、子组件A、子组件B 这种形式的布局需要进行数据传递时不要使用Link、prop这种每层都需要传递的方法了,改用@Provide + @Consume 这种形式,需要注意的是,被@Consume标记的组件不能传递到viewmodel中的进行二次赋值,只能在组件中直接赋值,否则会监听不到值的变化

2:MVVM开发模式问题

在之前的安卓和鸿蒙开发中,虽然说文件命名成了viewmodel,也确实将一部分数据操作放到了viewmodel中,但是还有很多的数据操作是在组件中进行的,这其实并不是彻底的viewmodel,仍然存在业务和UI的耦合,也做不到完全的数据驱动;之所以会出现这种情况,是之前并不知道如何在viewmodel中操作可以改变UI的变量,比如在组件中使用@state标记的按钮是否可以点击的变量,之前想的是通过viewmodel的方法将这个变量传进viewmodel中,再对这个变量进行操作;现在发现其实可以在viewmodel中使用@Track来标记这个变量,组件中只需要调用viewmodel中的变量即可,同时在组件中使用@state标记viewmodel,否则UI也可能不会变化

3:代码分层问题

在之前的开发中,我们一般将接口调用、业务逻辑、UI处理逻辑都放到viewmodel中,这样就导致viewmodel过于臃肿,这次我们引入了usecase概念对代码进行了更细致拆分:页面、组件等UI层只负责UI的渲染,不涉及任何的业务;viewmodel层是view和useCase的中转站,usecase对象创建在这里、半UI半业务的内容也放在这里;usecase则是纯业务,不涉及任何UI。

4:命名规范问题

A:接口返回的数据,以I开头,以Dto结尾命名,使用interface而非class,比如商品列表,命名为ICommodityListDto
B:业务中用到的数据,以Bo结尾命名,,使用class而非interface,比如商品列表,命名为CommodityListBo;同时禁止直接在业务中使用接口返回的数据
C:页面级别的组件,以Page结尾命名,比如商品列表,命名为CommodityListPage
D:页面中用到子组件,以View结尾命名,比如商品卡片,命名为CommodityCardView
E:调用接口、处理不涉及UI的业务逻辑的类,以UseCase结尾命名,比如商品列表,命名为CommodityListUseCase
F:view和useCase的中转站、处理半业务半UI的逻辑的类,以ViewModel结尾命名,比如商品列表,命名为CommodityListViewModel
通过规范命名,让代码的功能一目了然,层级更加清晰,开发逻辑更加明了

5:注释问题

A:对于Dto和Bo中非常规的字段,必须添加注释
B:对于UseCase和ViewModel中的方法,必须添加注释
C:对于page和view中的组件,必须添加注释
通过添加注释,增强代码的可阅读性和可理解性,减少再次修改代码时的难度

6:下面是一个Demo,用于展示以上情况,方便大家理解

A:page级组件

import { Colors } from 'uiResource';
import { CommodityListPagerView } from './CommodityListPagerView';
import { CommodityListSubPage } from './CommodityListSubPage';

//商品列表主页面
@Component
export struct CommodityListMainPage {
  @Provide page: string = '' //获取商品列表的参数

  build() {
    RelativeContainer() {
      CommodityListView() //商品列表
      CommodityListPagerView() //页码布局,布局中的page字段跟这里的page字段同步
    }
    .width('100%')
    .layoutWeight(1)
    .backgroundColor(Colors.bg_primary)
  }
}

B:子组件

import { Colors } from 'uiResource';
import { CommodityManageItemBo } from '../commodity/commodityList/CommodityManageItemBo';
import { CommodityListViewModel } from './CommodityListViewModel';

//商品列表
@Component
export struct CommodityListView {
  @Consume @Watch('onPageChange') page: string //获取商品列表的参数,与父组件同步状态
  @State viewModel: CommodityListViewModel = new CommodityListViewModel()
  private scroller: Scroller = new Scroller()

  build() {
    RelativeContainer() {
      this.list() //商品列表
      this.skeleton() //骨架屏
    }
    .width('100%')
    .layoutWeight(1)
    .backgroundColor(Colors.bg_primary)
  }

  @Builder
  list() {
    List({ scroller: this.scroller }) {
      LazyForEach(this.viewModel.commonDataSource,
        (commodityData: CommodityItemBo, itemGeneratorIndex: number) => {
          ListItem() {
          } //商品卡片
        }, (mCommodityData: CommodityItemBo,
          index: number) => mCommodityData.itemId.toString()) //这里的参数不建议缺省,缺省的话会造成渲染性能下降 : https://zhuanlan.zhihu.com/p/683854678
    }
    .width('100%')
    .height('100%')
    .edgeEffect(EdgeEffect.None)
    .cachedCount(4)
  }

  //骨架屏
  @Builder
  skeleton() {
    Row().visibility(this.viewModel.showSkeleton ? Visibility.Visible : Visibility.None)
  }

  async onPageChange() {
    this.viewModel.getCommodityList(this.page)
  }
}
import { Colors } from 'uiResource';

//页码条,可以输入页码
@Component
export struct CommodityListPagerView {
  @Consume page: number //获取商品列表的页码

  build() {
    RelativeContainer() {
      //例如输入框,可以输入页码
      //输入后改变页码 this.page = 10
    }
    .width('100%')
    .backgroundColor(Colors.bg_primary)
  }
}

C:UseCase 和 ViewModel

import { CommodityListApi } from "./CommodityListApi"
import { CommodityListBo } from "./CommodityListBo"
import { CommodityListConvertUtil } from "./CommodityListConvertUtil"


//商品列表的用例
export class CommodityListUseCase {
  private api: CommodityListApi = new CommodityListApi()
  private convertUtil: CommodityListConvertUtil = new CommodityListConvertUtil()

  //获取商品列表
  async getCommodityList(paramsBo: string): Promise<CommodityListBo> {
    let commodityListDto = await this.api.getCommodityList(paramsBo) ?? null
    let commodityListBo = new CommodityListBo()
    if (commodityListDto) {
      commodityListBo = this.convertUtil.convertCommodityListDto(commodityListDto)
    }
    return commodityListBo
  }
}
import { CommonDataSource } from 'uiBusiness'
import { CommodityListBo } from './CommodityListBo'
import { CommodityListUseCase } from './CommodityListUseCase'

@Observed
export class CommodityListViewModel {
  @Track commonDataSource: CommonDataSource<string> = new CommonDataSource([])
  @Track showSkeleton: boolean = false //是否展示骨架屏
  //获取商品列表
  private listUseCase: CommodityListUseCase = new CommodityListUseCase()

  //获取商品列表
  async getCommodityList(page: string) {
    this.showSkeleton = true
    try {
      let commodityListBo: CommodityListBo = await this.listUseCase.getCommodityList(page)
      //填充商品列表
      if (commodityListBo.list) {
        this.commonDataSource.deleteAll()
        this.commonDataSource.pushAllData(commodityListBo.list)
      }
    } catch (e) {
    }
    this.showSkeleton = false
  }
}

D:API、Dto、Bo、数据转换工具类

import { ApiService } from 'KNetworkService';
import { ICommodityListDto } from './ICommodityListDto';

export class CommodityListApi {
  //获取商品列表
  async getCommodityList(paramsBo: string): Promise<ICommodityListDto> {
    return ApiService.post<ICommodityListDto>({
      url: '接口地址',
      data: { paramsBo: paramsBo } //参数
    })
  }
}
export interface ICommodityListDto {
  list ?: Array<string>
}
@Observed
export class CommodityListBo {
  list: Array<string> = new Array()
}
import { ICommodityListDto } from './ICommodityListDto'
import { CommodityListBo } from './CommodityListBo'

//将dto转成bo,进行业务数据和接口数据的解耦
export class CommodityListConvertUtil {
  convertCommodityListDto(data: ICommodityListDto): CommodityListBo {
    let commodityListBo = new CommodityListBo()
    if (data.list != null) {
      for (let index = 0; index < data.list.length; index++) {
        commodityListBo.list.push(data.list[index])
      }
    }
    return commodityListBo
  }
}
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,458评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,030评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,879评论 0 358
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,278评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,296评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,019评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,633评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,541评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,068评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,181评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,318评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,991评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,670评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,183评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,302评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,655评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,327评论 2 358

推荐阅读更多精彩内容