ArkTS 严格模式实战:用类型模型守住 HarmonyOS 页面契约 ArkTS 严格模式对类型非常敏感。项目里一开始如果到处写临时对象、动态字段和Recordstring, ...后面很容易遇到编译错误或者页面之间字段对不上。这个桌面卡片工具项目把共享模型统一放在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保存后的数据才是CardRecordModelexport 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 是稳定内部 keyexport 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输出页面需要的视图模型。这样写的直接收益是页面少猜字段路由少丢参数资源映射更稳定严格模式下的编译错误也更容易定位。