ArkTS 严格模式实战:用类型模型守住 HarmonyOS 页面契约

ArkTS 严格模式对类型非常敏感。项目里一开始如果到处写临时对象、动态字段和 Record<string, ...>,后面很容易遇到编译错误,或者页面之间字段对不上。
这个桌面卡片工具项目把共享模型统一放在 AppModels.ets,再由服务层输出页面需要的视图模型。这样页面只关心“展示什么”,不关心底层数据从模板、本地卡片还是回收站来。
为什么先建模型
项目里有很多页面都在展示卡片:
- 首页的“我的卡片”
- 分类页的分类概览和热门卡片
- 卡片详情页的摘要卡
- 样式页的样式卡
- 管理页的卡片列表
- 桌面 Form 的摘要数据
如果每个页面自己定义字段,很快会出现字段不一致:
title
subtitle
value
badge
categoryId
templateId
cardId
imageKey
这些字段有些用于 UI,有些用于路由,有些用于资源映射。项目选择把它们抽成固定 interface。
ShowcaseCardModel:通用卡片展示模型
ShowcaseCardModel 是页面复用最高的模型:
export interface ShowcaseCardModel {
id: string;
title: string;
subtitle: string;
tone: ToneName;
value?: string;
footer?: string;
badge?: string;
route?: string;
imageKey?: string;
cardId?: string;
templateId?: string;
categoryId?: CardCategoryId;
}
它同时服务展示和跳转:
title、subtitle、value、footer负责文案。tone负责颜色主题。imageKey负责图片资源。cardId、templateId、categoryId负责点击后的语义。
这里没有把所有字段都做成必填。因为不同页面使用同一个卡片组件时,信息密度不同:详情页有完整卡片,分类概览可能只需要分类入口,样式页则更关注 imageKey。
MenuRowModel:列表行也需要明确目标参数
列表项模型和卡片模型类似,但更适合单行结构:
export interface MenuRowModel {
id: string;
mark: string;
title: string;
subtitle: string;
tone: ToneName;
value?: string;
route?: string;
tabId?: string;
imageKey?: string;
cardId?: string;
templateId?: string;
}
项目里曾经出现过“点击列表进详情但参数丢失”的问题。修复后的关键是:只要列表项会跳转详情,就必须带明确的 cardId 或 templateId。
页面处理时也按优先级判断:
const cardId: string = item.cardId ? item.cardId : '';
if (cardId.length > 0) {
router.pushUrl({
url: RoutePaths.cardDetail,
params: { cardId: cardId }
});
return;
}
const templateId: string = item.templateId ? item.templateId : item.id;
这样用户卡片和内置模板可以共用同一个详情页,但不会混成“空白 fallback”。
CardDraftModel 和 CardRecordModel:草稿和真实记录分开
编辑页使用的是 CardDraftModel,保存后的数据才是 CardRecordModel:
export interface CardDraftModel {
id: string;
templateId: string;
title: string;
subtitle: string;
detail: string;
value: string;
footer: string;
badge: string;
tone: ToneName;
categoryId: CardCategoryId;
favorite: boolean;
}
export interface CardRecordModel extends CardDraftModel {
active: boolean;
usageCount: number;
createdAt: string;
updatedAt: string;
lastUsedAt: string;
sceneTags: string[];
}
这个拆法有两个实际价值:
- 编辑页不需要伪造
createdAt、usageCount这类字段。 - 服务层保存时可以统一补齐运行时状态,页面不会乱写元数据。
枚举类型不要直接展示给用户
项目中的分类 ID 是稳定内部 key:
export type CardCategoryId =
'daily' | 'countdown' | 'health' | 'tool' |
'life' | 'study' | 'fun' | 'system';
这些 key 适合做持久化、筛选和路由参数,但不适合直接显示。详情页展示“类别”时没有直接渲染 countdown,而是调用:
appDataService.getCategoryLabel(this.card.categoryId)
这样用户看到的是“倒计时”“健康”“工具”这类中文文案。内部 key 和用户文案分离,是多语言、改文案和保持数据兼容的基础。
ArkTS 严格模式下的几个实践
这个项目里形成了几条比较稳定的规则:
ForEach的 item 和 key 函数都显式标注类型。- 共享常量优先用静态类或具名 interface。
- 自定义组件回调字段避免叫
onClick、onChange这类容易和内置属性混淆的名字。 - 避免在 UI Builder 中声明局部
const或let,需要中间值时抽 helper。 - 不直接渲染内部枚举 key,服务层负责转成用户文案。
例如首页分类卡片:
ForEach(appDataService.getCategoryCards('recommend', this.categoryQuery),
(item: ShowcaseCardModel) => {
GridItem() {
ShowcaseCard({
item: item,
compactBadge: true
})
}
},
(item: ShowcaseCardModel) => item.id
)
这比省略类型更啰嗦,但在严格模式下更稳。
视图模型比原始数据更适合页面
服务层可以把原始卡片、模板目录、分类元信息统一转换成页面模型。以详情页为例:
export interface CardDetailViewModel {
card: CardRecordModel;
isTemplate: boolean;
}
页面只需要根据 isTemplate 决定按钮:
- 模板:显示“添加到我的卡片”
- 我的卡片:显示“编辑当前卡片”
- 空白 fallback:显示“新建卡片”
底层是模板还是用户数据,页面不直接判断。
小结
ArkTS 项目里,类型模型不是可有可无的“文档”,而是页面契约。这个桌面卡片工具项目用 AppModels.ets 把卡片、列表、草稿、记录、统计、备份、提醒都统一建模,再由 AppDataService 输出页面需要的视图模型。
这样写的直接收益是:页面少猜字段,路由少丢参数,资源映射更稳定,严格模式下的编译错误也更容易定位。
更多推荐


所有评论(0)