欢迎光临
我们一直在努力

第二篇:ArkTS 工程拆分实战:健康菜谱助手为什么要做三层架构

如果一个 HarmonyOS 项目只有一个页面,怎么写都能跑;但健康菜谱助手不是单页应用,它有首页、分类、详情、收藏、阅读、朗读、元服务和服务卡片。页面一多,真正的问题就变成:数据放哪里、状态谁维护、跳转怎么收口、公共样式如何统一。

这篇文章专门讲工程拆分。它不像第一篇那样从项目目标出发,而是站在代码维护角度,解释为什么要把页面层、服务层、模型层和公共资源分开。

关键词:ArkTS、三层架构、ArkUI、服务层、工程维护

从页面膨胀开始说

图 1:从页面膨胀开始说

很多 ArkUI 初学项目会把所有内容写进一个 Page:数组写在页面里,搜索写在页面里,收藏也写在页面里。刚开始看起来很快,但当首页要跳详情、详情要改收藏、收藏页要刷新、阅读页要记录历史时,这种写法会让每个页面都知道太多事情。

健康菜谱助手采用更稳妥的方式:页面只处理展示和用户交互;服务层负责读写数据、组织业务结果;模型层定义 Recipe、Article、Ingredient、ReadingRecord 这类结构;公共目录放主题、尺寸、通用组件和工具方法。

仓储接口示例

export interface RecipeRepository {
  search(keyword: string): Recipe[]
  findById(id: string): Recipe | undefined
  getDaily(count: number): Recipe[]
}

export class LocalRecipeRepository implements RecipeRepository {
  private readonly recipes: Recipe[] = RECIPE_DATA

  search(keyword: string): Recipe[] {
    const key = keyword.trim()
    if (key.length === 0) {
      return []
    }
    return this.recipes.filter((recipe: Recipe) =>
      recipe.name.includes(key)
        || recipe.tags.some((tag: string) => tag.includes(key))
        || recipe.ingredients.some((item: Ingredient) => item.name.includes(key))
    )
  }
}

依赖方向要单向

图 2:依赖方向要单向

依赖方向越清楚,项目越不容易乱。产品页面可以依赖服务,服务可以依赖模型和数据,但模型不应该反过来依赖页面。公共组件也不能偷偷调用业务页面,否则后面做元服务或卡片时就会出现无法复用的问题。

这也是为什么我不建议把所有工具都扔到一个 utils 文件里。工具如果没有边界,最后会变成任何模块都可以随便调用的杂物间。项目规模不大时就建立边界,后面扩展反而更省力。

状态管理的分级

图 3:状态管理的分级

页面内部状态使用 @State 即可,比如搜索结果、当前轮播索引、朗读按钮状态。跨页面状态才提升到 AppStorage,比如当前底部 Tab、当前断点、主题色。持久数据不应该直接放在 AppStorage 里长期保存,而应由服务层写入 Preferences 或其他本地存储。

这种分级可以避免两个问题:一是页面刷新时状态来源混乱;二是持久数据和 UI 状态混在一起,导致后续迁移或清理数据变得困难。

主题资源集中管理

图 4:主题资源集中管理

菜谱类应用非常依赖视觉一致性。按钮、卡片、标签、标题、图片圆角和底部导航如果各写各的,页面会很快显得拼凑。项目里应该把颜色、间距、圆角、字体大小、阴影等基础 token 收拢起来,再由各页面引用。

除了样式统一,这样做还有审核意义。AppGallery 对布局、对比度、深浅色和文本可读性都很敏感。颜色分散在页面里时,很难系统性检查;颜色集中后,修改和验证都更可控。

为什么服务层比工具函数更重要

图 5:为什么服务层比工具函数更重要

很多项目会把搜索、收藏、阅读记录都写成 utils 函数,但工具函数通常没有明确生命周期,也不表达业务意图。服务层不只是函数集合,它代表一组稳定能力。比如 RecipeService 负责菜谱查询,UserDataManager 负责用户本地数据,SpeechReader 负责朗读状态。名称本身就能说明责任边界。

当一个功能出问题时,服务层能帮助定位。搜索结果不对,就查 RecipeRepository;收藏没刷新,就查 UserDataManager;朗读失败,就查 SpeechReader。没有服务边界时,所有问题都会回到页面里,维护成本会快速上升。

模块拆分对元服务的影响

元服务模块不能直接依赖主应用页面,否则会破坏轻量入口的意义。atomicservice 应该复用数据模型、主题资源和少量通用能力,但不应该把 HomePage 或完整底部导航搬过去。主应用和元服务共享的是业务口径,而不是页面结构。

这种拆法也能避免包体和启动成本失控。元服务的用户预期是快速触达,如果进入后还加载一大堆主应用状态,体验会变慢。架构在这里不是抽象概念,而是直接影响用户感知。

落地细节:目录不是越多越好

三层架构不等于创建很多目录。目录越多,如果没有明确边界,反而会让开发者不知道文件该放哪。健康菜谱助手的目录拆分遵循一个简单原则:能被多个页面复用的放 common,和业务强相关的放 services 或 data,只负责页面展示的放 pages。

比如 RecipeCard 这种多个页面都可能用到的组件,可以放在 common components;但 HomePage 里的朗读入口卡片如果只服务首页,就不必为了复用而提前抽到公共层。过早抽象会让公共目录变重,也会让组件参数越来越复杂。

服务层也要控制数量。不要为每个按钮都创建一个 service,而是按业务能力划分。菜谱查询是一类,用户本地数据是一类,朗读控制是一类,发布材料不进入运行时代码。这样的边界更符合长期维护。

架构边界的实际收益

三层架构在这个项目里的价值,首先体现在返工成本上。首页、分类页和收藏页都会使用菜谱数据,如果每个页面各自维护一份数组,后续调整字段时要改很多地方;把 Recipe 模型和查询服务收口后,字段变化只需要集中处理。阅读文章、朗读状态和本地记录也是同样的道理,页面只关心展示,服务负责数据和状态规则。

第二个收益是可测试性。纯模型和服务函数不依赖 ArkUI 组件,可以用简单输入输出验证搜索、过滤、收藏、去重、排序等逻辑。页面测试成本高,服务测试成本低,把复杂逻辑放进服务层,实际就是把风险从难测区域迁移到易测区域。这个思路在小项目里也值得坚持。

ArkTS 代码组织的细节

ArkTS 比普通 JavaScript 更强调类型和结构。健康菜谱助手里的 Recipe、Article、Ingredient、ReadingSegment 等结构最好都声明为明确接口,不依赖随手拼出来的对象。这样在页面调用时,字段缺失会尽早暴露,而不是等运行时点击某个详情页才发现 undefined。

公共组件也要控制边界。比如菜谱卡片可以复用,但它不应该直接读取收藏服务;收藏状态应该由父页面或 ViewModel 传入。这样同一个卡片既能在首页展示,也能在分类、收藏、卡片预览或元服务页面里复用。组件越干净,后续改版越轻松。

维护时最容易暴露的架构问题

一个项目刚写完时,看不出架构是否合理;等到需求变更时,问题才会暴露。比如首页想新增朗读入口,如果首页和阅读模块耦合过深,就会牵动很多页面;收藏页想同步详情页状态,如果收藏逻辑散落在组件里,就很容易出现图标和数据不同步。

健康菜谱助手把模型、服务和组件分开,就是为了让后续改动更集中。新增一个推荐策略,优先改服务;新增一个展示样式,优先改组件;新增一个页面入口,优先改路由和组合层。每次变更都能找到主要落点,项目就不会越改越乱。

架构文章的最终补充

如果把健康菜谱助手继续迭代下去,架构还要承担更多变化。比如新增购物清单时,详情页、收藏页和搜索结果都可能产生入口;新增朗读进度时,阅读页和本地数据服务都要协作;新增卡片刷新时,主应用数据和卡片展示也要保持一致。提前划清边界,就是为了这些变化到来时不用推倒重写。

所以这篇架构文章的重点不是让目录看起来复杂,而是让每个模块有稳定职责。页面负责表达,服务负责规则,模型负责结构,资源负责统一视觉。只要这个方向不乱,后续添加功能就像往已经整理好的架子上放东西,而不是每次都重新找位置。

架构文章的评分关键:不要只画图,要解释边界

架构类文章最容易写空。高质量写法不是只说“三层架构很好”,而是说明每一层为什么存在、解决了什么问题、如果不拆会产生什么风险。健康菜谱助手里,页面层负责展示,服务层负责业务规则,模型层负责数据结构,资源层负责视觉一致性,这些边界都能对应具体功能。

发布到 CSDN 时,摘要建议强调:本文以健康菜谱助手为例,讲解 ArkTS 项目中页面、服务、模型和资源的拆分方式,并结合搜索、收藏、阅读记录和元服务卡片说明工程边界如何降低后续迭代成本。

架构代码要体现依赖方向

读者最喜欢能直接借鉴的代码片段。下面的写法强调服务接口和实现分离,页面只依赖接口能力,不直接关心数据来源:

```ts
export interface FavoriteService {
  isFavorite(recipeId: string): boolean
  toggle(recipeId: string): Promise<boolean>
  list(): Recipe[]
}

export class LocalFavoriteService implements FavoriteService {
  private ids: Set<string> = new Set<string>()

  isFavorite(recipeId: string): boolean {
    return this.ids.has(recipeId)
  }

  async toggle(recipeId: string): Promise<boolean> {
    if (this.ids.has(recipeId)) {
      this.ids.delete(recipeId)
      return false
    }
    this.ids.add(recipeId)
    return true
  }
}
```

这类代码片段能让架构文章落到具体实现。文章里再解释页面如何调用、数据如何持久化、后续如何替换存储,就会比单纯讲目录结构更有技术含量。

第二篇小结

架构的价值不是炫技,而是减少后续返工。健康菜谱助手通过页面、服务、模型、公共资源的拆分,让首页、朗读、收藏和元服务都能在各自边界内演进。下一篇会把视角切回用户第一眼看到的地方:首页。

写在最后:欢迎体验项目成果

如果你想看看这套 ArkTS 架构最终落到应用里是什么效果,可以下载体验健康菜谱助手。实际使用首页、收藏、阅读和卡片入口后,再回头看架构拆分会更直观。也欢迎评论交流你在 HarmonyOS 项目分层时遇到的问题。

赞(0)
未经允许不得转载:171主机测评 » 第二篇:ArkTS 工程拆分实战:健康菜谱助手为什么要做三层架构
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址