HarmonyOS 应用开发《掌上英语》第16篇:枚举与常量管理:RouterMap 的设计模式
枚举与常量管理:RouterMap 的设计模式

一、引言
在大型应用中,路由路径的管理是一件容易被忽视但影响深远的事情。如果每个页面跳转都直接使用字符串字面量,一旦路径需要修改,将面临全局搜索替换的噩梦——改漏一个就会导致页面跳转失败。
我们英语学习 App 包含 40+ 个页面横跨 4 个 feature 模块,路由管理必须做到集中、类型安全、易于维护。RouterMap 枚举 + RouterTable 注册表 + RouterModule 封装的三层架构正是为此而设计。本文重点分析第一层——RouterMap 枚举的设计模式。
二、RouterMap:字符串枚举的路由表
2.1 完整定义
// commons/commonLib/src/main/ets/constants/RouterMap.ets
export enum RouterMap {
// 主页
MAIN_PAGE = 'MainPage',
// 登录
LOGIN_PAGE = 'LoginPage',
// -----------------------------------------------------练习
CHAPTER_PRACTICE_PAGE = 'ChapterPractice',
MATERIAL_DOWNLOAD_PAGE = 'MaterialDownload',
FEATURE_COURSE_PAGE = 'FeatureCourses',
SEARCH_PAGE = 'SearchPage',
SEARCH_INPUT_PAGE = 'SearchQuestionPage',
// -----------------------------------------------------课程
ANSWER_QUESTIONS_PAGE = 'AnswerQuestionsPage',
ANSWER_QUESTIONS_TWO_PAGE = 'AnswerQuestionsTwoPage',
ANSWER_REPORT_PAGE = 'TestReportPage',
EXAM_RESULT_PAGE = 'ExamResultPage',
GOOD_COURSE_DETAIL_PAGE = 'GoodCourseDetailPage',
MY_WRONG_PAGE = 'MyWrongPage',
MY_COLLECTION_PAGE = 'MyCollectionPage',
MY_NOTE_PAGE = 'MyNotePage',
View_NOTE_PAGE = 'ViewNotePage',
Mock_PAGE = 'MockTestPage',
COURSE_INTRODUCTION_PAGE = 'CourseIntroductionPage',
Level_TWO_PAGE = 'SecondListPage',
Level_THREE_PAGE = 'ThirdListPage',
Level_ONE_PAGE = 'TopicHomePage',
// -----------------------------------------------------我的
MESSAGE_CENTER_PAGE = 'MessageCenterPage',
SET_UP_PAGE = 'SetUpPage',
FEEDBACK_PAGE = 'FeedbackPage',
FEEDBACK_RECORD_PAGE = 'FeedbackRecordPage',
PRIVACY_STATEMENT_PAGE = 'PrivacyStatementPage',
PRIVACY_AGREEMENT_PAGE = 'PrivacyAgreementPage',
PRIVACY_USE_PAGE = 'PrivacyUseAlertPage',
PERSONAL_CENTER_PAGE = 'EditPersonalCenter',
ABOUT_PAGE = 'AboutPage',
COURSE_PAGE = 'CoursePage',
PRACTICE_RECORDS_PAGE = 'PracticeRecordsPage',
PRACTICE_DETAILS_PAGE = 'PracticeDetailsPage',
DAY_PRACTICE_DETAILS_PAGE = 'OneDayPracticeRecordsPage',
COLLECTION_PAGE = 'CollectionPage',
LEARNING_PLAN_PAGE = 'LearningPlanSettingPage',
REMINDER_SETTING_PAGE = 'ReminderSettingPage',
MY_ORDER_PAGE = 'MyOrderPage',
Order_Detail_PAGE = 'OrderDetailPage',
BROWSING_HISTORY_PAGE = 'BrowsingHistoryPage',
DASHBOARD_PAGE = 'DashboardPage',
DAILY_CHALLENGE_PAGE = 'DailyChallengePage',
WORD_CARD_PAGE = 'WordCardPage',
SPEECH_EVAL_PAGE = 'SpeechEvalPage',
}
2.2 关键设计决策
为什么用字符串枚举而非数字枚举?
ArkTS 的枚举默认是数字枚举(MAIN_PAGE = 0),但 RouterMap 选择的是字符串枚举。原因有三:
-
可读性:
RouterMap.MAIN_PAGE的值是'MainPage',在调试、日志和序列化时一目了然。 -
序列化友好:路由路径通常需要在配置文件中使用(如
route_map.json),字符串值天然支持 JSON 序列化。 -
与 NavPathStack 兼容:HarmonyOS 的
NavPathStack.pushPathByName()需要字符串类型的路由名,字符串枚举直接满足要求。
// RouterModule.push 的实现
public static push(info: NavRouterInfo, animated?: boolean) {
RouterModule.stack.pushPathByName(info.url, info.param); // url 为字符串
}
为什么用注释分组而非嵌套枚举?
你可能注意到代码中使用注释 // --- 练习 --- 等来分组。为什么不使用嵌套枚举或命名空间?因为 ArkTS 不支持枚举嵌套。注释分组是最务实的替代方案——它虽然没有编译期约束,但配合 IDE 的代码折叠功能,阅读体验尚可。
三、RouterTable:Builder 注册表
有了枚举定义的路由名,还需要将它们与实际的页面 Builder 关联起来。这就是 RouterTable 的作用:
// product/entry/src/main/ets/model/RouterTable.ets
import { RouterMap } from 'commonlib';
export class RouterTable {
static builderMap: Map<string, WrappedBuilder<[object]>> = new Map();
public static routerInit() {
// 主页
RouterTable.builderMap.set(RouterMap.MAIN_PAGE, wrapBuilder(MainEntryBuilder));
RouterTable.builderMap.set(RouterMap.LOGIN_PAGE, wrapBuilder(LoginPageBuilder));
// 练习模块(来自 homepage feature)
RouterTable.builderMap.set(RouterMap.CHAPTER_PRACTICE_PAGE, wrapBuilder(ChapterPracticeBuilder));
RouterTable.builderMap.set(RouterMap.SEARCH_PAGE, wrapBuilder(SearchPageBuilder));
// 课程模块(来自 topicpage feature)
RouterTable.builderMap.set(RouterMap.ANSWER_QUESTIONS_PAGE, wrapBuilder(AnswerQuestionsBuilder));
RouterTable.builderMap.set(RouterMap.WORD_CARD_PAGE, wrapBuilder(WordCardPageBuilder));
RouterTable.builderMap.set(RouterMap.SPEECH_EVAL_PAGE, wrapBuilder(SpeechEvalPageBuilder));
// 我的模块(来自 minepage feature)
RouterTable.builderMap.set(RouterMap.SET_UP_PAGE, wrapBuilder(SetUpPageBuilder));
RouterTable.builderMap.set(RouterMap.LEARNING_PLAN_PAGE, wrapBuilder(LearningPlanSettingPageBuilder));
RouterTable.builderMap.set(RouterMap.DASHBOARD_PAGE, wrapBuilder(DashboardPageBuilder));
// ... 40+ 路由注册
}
}
设计要点:
builderMap以RouterMap枚举值为 key,以WrappedBuilder为 value- 所有注册集中在
routerInit()中统一调用,在应用启动时完成 wrapBuilder()将@Builder函数包装为可反射调用的形式
四、RouterModule:统一导航封装
有了路由表和枚举,还需要一个统一的导航工具类来消费它们:
// commons/commonLib/src/main/ets/utils/RouterModule.ets
class RouterModule {
public static stack: NavPathStack = new NavPathStack();
// 页面跳转
public static push(info: NavRouterInfo, animated?: boolean) {
RouterModule.stack.pushPathByName(info.url, info.param, info.onPop, animated);
}
// 页面替换
public static replace(info: NavRouterInfo) {
RouterModule.stack.replacePathByName(info.url, info.param);
}
// 页面回退
public static pop<T = boolean>(result?: T, animated?: boolean) {
RouterModule.stack.pop(result, animated);
}
// 页面栈清空
public static clear(animated?: boolean) {
RouterModule.stack.clear(animated);
}
}
业务代码中使用时,RouterMap 枚举值 + RouterModule 的组合确保了类型安全:
// MainPage.ets - 页面跳转示例
RouterModule.push({ url: RouterMap.WORD_CARD_PAGE }); // 无参数跳转
RouterModule.push({ url: RouterMap.LOGIN_PAGE, param: false }); // 带参数跳转
RouterModule.push({ url: RouterMap.ANSWER_QUESTIONS_PAGE, param: '1' }); // 字符串参数
RouterModule.push({ url: RouterMap.WORD_CARD_PAGE, param: { id: 1 } }); // 对象参数
对比直接使用字符串字面量:
// ❌ 不好的做法:硬编码字符串
RouterModule.push({ url: 'WordCardPage' }); // 拼写错误不会报错
RouterModule.push({ url: 'word-card-page' }); // 大小写不一致
// ✅ 好的做法:使用枚举
RouterModule.push({ url: RouterMap.WORD_CARD_PAGE }); // 拼写错误编译报错
五、route_map.json:与系统路由的桥接
除了代码层面的路由管理,HarmonyOS 还要求每个 feature 模块在 route_map.json 中注册 NavDestination 路由:
{
"routerMap": [
{
"name": "HomePage",
"pageSourceFile": "src/main/ets/pages/MainPage.ets",
"buildFunction": "HomePageBuilder",
"data": {
"description": "首页"
}
},
{
"name": "FeaturedCourses",
"pageSourceFile": "src/main/ets/pages/FeaturedCourses.ets",
"buildFunction": "FeaturedCoursesBuilder",
"data": {
"description": "精选课程"
}
}
]
}
这里的 name 值必须与 RouterMap 枚举的值一致,否则运行时无法匹配。因此 RouterMap 枚举值实际上起着代码层路由名 ↔ 系统层路由配置的桥梁作用。
六、字符串枚举 vs 数字枚举 vs 常量对象
ArkTS 中定义常量有三种主要方式,各有优劣:
| 方案 | 示例 | 运行时值 | 序列化 | IDE 提示 | 反向映射 |
|---|---|---|---|---|---|
| 字符串枚举 | enum E { A = 'a' } |
'a' |
✅ 友好 | ✅ 完整 | ❌ |
| 数字枚举 | enum E { A } |
0 |
❌ 无意义 | ✅ 完整 | ✅ |
| 常量对象 | const E = { A: 'a' } |
'a' |
✅ 友好 | ⚠️ 有限 | ❌ |
对于路由管理,字符串枚举是最佳选择,因为:
- 枚举名即文档——
RouterMap.WORD_CARD_PAGE比'wordCardPage'更具语义 - 重构友好——修改枚举值时,所有引用处自动更新
- 不可变——
const enum在编译期内联,无运行时开销 - 统一管理——所有路由路径在一处定义,一目了然
七、最佳实践
-
统一枚举命名规范:
模块_功能_PAGE,如LEARNING_PLAN_PAGE、ANSWER_QUESTIONS_PAGE。清晰的命名让开发者看到枚举名就能定位到对应模块。 -
注释分组:使用
// --- 模块名 ---注释对枚举成员进行分组,配合 IDE 代码折叠提升可读性。 -
枚举值尽量简短:枚举的值用于路由匹配,宜短不宜长。例如
'WordCardPage'而非'WordCardManagementPage'。 -
与 route_map.json 保持同步:新增路由时,确保
RouterMap枚举、route_map.json、RouterTable.routerInit()三者同步更新。 -
参数类型关联:对于带参数的路由,建议在跳转处加上注释说明参数类型:
RouterModule.push({
url: RouterMap.ANSWER_QUESTIONS_PAGE,
param: '1' as string, // ⬅️ param: 章节ID(字符串)
});
八、总结
RouterMap 枚举 + RouterTable 注册表 + RouterModule 封装的三层路由架构,是我们项目中的核心基础设施:
- RouterMap:用字符串枚举集中定义 40+ 路由路径,杜绝硬编码
- RouterTable:将枚举值映射到具体的页面 Builder,实现声明式路由注册
- RouterModule:在 NavPathStack 之上提供统一的 push/pop/replace/clear 接口
这套模式的核心价值在于:一处修改,全局生效。当你需要修改某个路由路径时,只需更新 RouterMap 枚举值,所有引用处自动适配。对于大型多模块项目而言,这种"集中管理"的设计模式是保证代码质量的关键。
更多推荐


所有评论(0)