OpenHarmony 英语学习 App 实战:ArkTS 项目结构、页面路由与模块化拆分

摘要

当一个 ArkUI 项目从几个页面增长到二十多个页面时,最容易出现的问题不是某个功能不会写,而是结构开始混乱:页面之间互相依赖、工具函数散落、数据模型重复定义、路由路径难以维护。本文以「英语视界 YingYu」项目为例,分享 OpenHarmony/HarmonyOS ArkTS 项目如何做页面路由和模块化拆分。🧩

本文重点包括:

  • 目录分层;
  • 页面注册;
  • 路由跳转;
  • 数据模型集中管理;
  • 工具模块拆分;
  • 页面组件拆分;
  • 后续可维护性建议。

一、项目结构总览

项目核心 ArkTS 代码位于:

entry/src/main/ets/
├── MainTabs.ets
├── entryability/
│   └── EntryAbility.ets
├── pages/
├── components/
├── data/
├── model/
├── styles/
└── utils/

这是一种比较清晰的中型应用结构:

  • entryability:应用生命周期;
  • pages:页面;
  • components:复用 UI 组件;
  • data:静态学习内容;
  • model:类型定义;
  • utils:业务工具;
  • styles:主题和设计 token。

二、EntryAbility:只做应用级初始化

EntryAbility.ets 负责:

  • 设置颜色模式;
  • 初始化用户数据;
  • 初始化分布式同步;
  • 加载主页面;
  • 处理前后台生命周期;
  • 处理跨设备续接。
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET)
  initYingYuUserData(this.context)
  initDistributedStore(this.context)
}

窗口创建时加载页面:

onWindowStageCreate(windowStage: window.WindowStage): void {
  windowStage.loadContent('pages/MainTabsV2', (err) => {
    if (err.code) {
      windowStage.loadContent('pages/MainTabs', () => {})
    }
  })
}

入口 Ability 不应该写具体业务 UI,这样应用启动逻辑才清爽。

三、页面注册:main_pages.json

所有可路由页面都在 main_pages.json 中注册:

{
  "src": [
    "pages/Index",
    "pages/Home",
    "MainTabs",
    "pages/MainTabsV2",
    "pages/Vocabulary",
    "pages/VocabularyDetail",
    "pages/DailySentence",
    "pages/Listening",
    "pages/FunEnglish",
    "pages/FunEnglishDetail",
    "pages/Settings",
    "pages/CustomWordBook",
    "pages/ReviewCenter",
    "pages/DailyTask",
    "pages/LearningCalendar",
    "pages/LearningGoals",
    "pages/Grammar",
    "pages/PhraseStudy",
    "pages/ShareCenter"
  ]
}

页面注册的好处是路由清晰,但也要求团队对路径命名保持一致。

四、路由跳转

页面间跳转使用 @ohos.router

import router from '@ohos.router'

首页快捷入口中有大量路由跳转:

this.QuickAccessTile(
  '复习中心',
  '艾宾浩斯',
  $r('sys.symbol.arrow_2_circlepath'),
  $r('app.color.icon_tag_grade'),
  () => router.pushUrl({ url: 'pages/ReviewCenter' })
)

返回上一页:

SymbolGlyph($r('sys.symbol.chevron_left'))
  .onClick(() => router.back())

路由建议:

  • 页面路径统一放在常量文件中;
  • 高频页面入口集中管理;
  • 避免字符串路径散落在过多页面中。

五、Tabs 作为主导航

项目使用 Tabs 做主导航,再用自定义底部导航控制切换:

Tabs({
  index: this.currentIndex,
  controller: this.tabsController
}) {
  TabContent() {
    this.HomeTab()
  }
  TabContent() {
    this.StudyTab()
  }
  TabContent() {
    this.PracticeTab()
  }
  TabContent() {
    this.ProfileTab()
  }
}
.barPosition(BarPosition.End)

切换时:

this.tabsController.changeIndex(index)

这种方式把主页面分成首页、学习、练习、我的四大区域,符合学习类 App 的信息架构。

六、数据模型集中管理

项目中的模型集中在 DataModels.ts

export interface Word {
  id: number
  word: string
  phonetic: string
  translation: string
  example: string
  exampleTranslation: string
  grade: number
}

复习记录:

export interface ReviewRecord {
  wordId: string
  wordType: 'vocabulary' | 'custom' | 'funEnglish' | 'grammar'
  learningDate: string
  reviewDates: string[]
  nextReviewDate: string
  easeFactor: number
  interval: number
  repetitions: number
}

集中管理模型的好处:

  • 页面和工具函数共享类型;
  • 减少重复定义;
  • 后续接口对接更容易;
  • 编译期能发现字段错误。

七、data 目录:内置学习内容

项目把静态学习内容放在 data 目录:

data/
├── VocabularyData.ts
├── DailySentenceData.ts
├── ListeningData.ts
├── FunEnglishData.ts
├── GrammarData.ts
└── PhraseData.ts

这种结构适合当前阶段:

  • 词库体量不大;
  • 听力和语法内容可内置;
  • 离线也可学习;
  • 页面加载速度快。

后续如果内容量增长,可以逐步迁移到服务端或按需下载。

八、utils 目录:业务能力模块化

utils 目录承担项目的业务能力:

utils/
├── UserDataManager.ts
├── StorageService.ts
├── SpacedRepetition.ts
├── ShareService.ts
├── DistributedSync.ets
├── ListeningTtsHelper.ets
├── YingYuPreferences.ts
├── DeviceUtils.ets
└── AdaptiveDimensions.ets

每个工具文件对应一个能力:

  • 用户数据;
  • 存储服务;
  • 复习算法;
  • 分享能力;
  • 分布式同步;
  • TTS;
  • 持久化;
  • 设备判断;
  • 响应式尺寸。

这比把所有函数写进页面文件要清晰得多。

九、组件拆分:页面内也可以拆子组件

CustomWordBook.ets 中,添加单词和详情弹窗被拆成独立组件:

@Component
struct AddWordSheet {
  @Link visible: boolean
  onWordAdded: () => void = () => {}
}

详情弹窗:

@Component
struct WordDetailSheet {
  @Link visible: boolean
  @Prop word: CustomWord = {
    id: '',
    word: '',
    translation: '',
    tags: [],
    createdAt: '',
    reviewCount: 0,
    mastered: false
  }
}

这种拆分能减少主页面复杂度,也避免输入框状态变化导致整页频繁重建。

十、存储层封装

底层 Preferences 封装在 YingYuPreferences.ts

export function yingyuPrefSet(key: string, value: string): void {
  if (!pref) {
    AppStorage.setOrCreate(key, value)
    return
  }
  pref.putSync(key, value)
  void pref.flush()
}

业务层再通过 UserDataManager.tsStorageService.ts 读写学习数据。这样的层次是:

页面
  -> UserDataManager / StorageService
  -> YingYuPreferences / AppStorage
  -> Preferences

页面不直接接触底层存储细节,后续迁移更容易。

十一、模块化建议

继续扩展项目时,可以进一步优化:

  1. 抽出 Routes.ts 统一管理页面路径;
  2. 把首页大文件拆成多个组件文件;
  3. 把成就规则拆成独立 AchievementService
  4. 把复习算法和 UI 更新逻辑完全统一;
  5. StorageService 加数据版本号;
  6. 给静态数据增加按年级索引;
  7. 把 AI 接口模型同步到移动端类型定义。

这些优化不是一开始必须做,但项目继续变大时会很有价值。

十二、小结

本文结合「英语视界 YingYu」项目,梳理了 OpenHarmony ArkTS 项目的模块化拆分:

  • EntryAbility 负责应用级初始化;
  • main_pages.json 管理页面注册;
  • router.pushUrl() 负责页面跳转;
  • Tabs 构建主导航;
  • DataModels.ts 集中定义类型;
  • data 目录放内置学习内容;
  • utils 目录封装业务能力;
  • 页面内复杂弹窗可拆成子组件;
  • 存储层通过服务封装,避免页面直接操作底层。

项目越大,结构越重要。好的模块化不是为了“看起来高级”,而是为了让下一次加功能时不痛苦。🧱

img

img

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐