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
}
}