HMRouter 是目前鸿蒙生态中功能最完善的路由框架,它封装了系统 Navigation 的复杂细节,用注解方式让页面跳转变得简单高效。本文将带你从零开始,全面掌握 HMRouter 的配置、跳转、拦截、生命周期等核心能力。
一、为什么是 HMRouter?
在 HarmonyOS 中,官方提供了两种路由方案:Router和Navigation。Router 是早期的页面路由方案,存在 32 条页面栈上限、不支持路由拦截等限制,官方已不再推荐使用 。Navigation 是当前官方主推的组件化导航方案,功能更强大,但在实际开发中仍有一些痛点:
每个页面都需要手动包裹
NavDestination,增加代码层级缺少统一的路由拦截机制
页面生命周期管理需要自行实现
跨模块跳转配置繁琐
HMRouter正是为解决这些问题而生。它在底层封装了 Navigation 的系统能力,提供了一套基于注解的声明式路由方案,让开发者几乎无需关心 Navigation 的复杂细节 。
HMRouter 核心能力一览
| 能力 | 说明 |
|---|---|
| 📍 注解驱动 | @HMRouter声明页面路径,@HMInterceptor定义拦截器 |
| 🔀 跨模块跳转 | 完美支持 HAR/HSP 之间的页面跳转 |
| 🚦 路由拦截 | 支持全局/局部拦截器,可实现登录校验、权限控制 |
| 🔄 页面生命周期 | 自定义生命周期处理器,监听页面事件 |
| ✨ 自定义转场动画 | 支持页面级和全局转场动画配置 |
| 📦 单例页面 | 通过singleton: true实现页面复用 |
| 🪟 Dialog 页面 | 将页面以弹窗形式展示 |
| 📤 返回传参 | 支持页面返回时携带参数 |
二、快速上手:从零配置 HMRouter
2.1 安装依赖
首先安装 HMRouter 核心库:
bash
ohpm install @hadss/hmrouter
如果需要使用自定义转场动画,还需安装过渡动画库:
bash
ohpm install @hadss/hmrouter-transitions
2.2 配置编译插件
修改工程根目录的hvigor/hvigor-config.json,添加路由编译插件依赖 :
json
{ "dependencies": { "@hadss/hmrouter-plugin": "^1.0.0-rc.10" } }然后在使用 HMRouter 的模块(entry/har/hsp)的hvigorfile.ts中引入插件:
entry 模块(Hap):
typescript
// entry/hvigorfile.ts import { hapTasks } from "@ohos/hvigor-ohos-plugin"; import { hapPlugin } from "@hadss/hmrouter-plugin"; export default { system: hapTasks, plugins: [hapPlugin()] };Har 模块:使用harPlugin()
Hsp 模块:使用hspPlugin()
2.3 工程配置
在build-profile.json5中配置useNormalizedOHMUrl为true,使框架能动态导入模块 :
json
{ "buildOption": { "useNormalizedOHMUrl": true } }2.4 初始化路由框架
在EntryAbility的onCreate中初始化HMRouterMgr:
typescript
// entry/src/main/ets/entryability/EntryAbility.ets import { HMRouterMgr } from '@hadss/hmrouter'; export default class EntryAbility extends UIAbility { onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { HMRouterMgr.init({ context: this.context }); } }2.5 定义路由入口
在首页中使用HMNavigation作为路由容器 :
typescript
// entry/src/main/ets/pages/Index.ets import { HMDefaultGlobalAnimator, HMNavigation, HMRouterMgr } from '@hadss/hmrouter'; import { AttributeUpdater } from '@kit.ArkUI'; class NavModifier extends AttributeUpdater<NavigationAttribute> { initializeModifier(instance: NavigationAttribute): void { instance.mode(NavigationMode.Stack); instance.hideTitleBar(true); instance.hideToolBar(true); } } @Entry @Component export struct Index { modifier: NavModifier = new NavModifier(); build() { Column() { HMNavigation({ navigationId: 'mainNavigation', options: { standardAnimator: HMDefaultGlobalAnimator.STANDARD_ANIMATOR, dialogAnimator: HMDefaultGlobalAnimator.DIALOG_ANIMATOR, modifier: this.modifier } }) { // 首页内容 Button('跳转到登录页') .onClick(() => { HMRouterMgr.push({ pageUrl: 'LoginPage' }); }) } } .height('100%') .width('100%') } }2.6 创建第一个页面
使用@HMRouter注解声明页面 :
typescript
// entry/src/main/ets/views/LoginPage.ets import { HMRouter } from '@hadss/hmrouter'; @HMRouter({ pageUrl: 'LoginPage' }) @Component export struct LoginPage { build() { Column() { Text('登录页面') Button('返回') .onClick(() => { HMRouterMgr.pop(); }) } .width('100%') .height('100%') .justifyContent(FlexAlign.Center) } }注意事项:
@HMRouter装饰的组件必须使用export导出默认页面目录为
src/main/ets/components,可通过hmrouter_config.json的scanDir自定义
三、页面跳转与传参
3.1 基础跳转
typescript
// push:压栈跳转,保留当前页面 HMRouterMgr.push({ pageUrl: 'ProductDetail' }); // replace:替换当前页面 HMRouterMgr.replace({ pageUrl: 'ProductDetail' }); // pop:返回上一页 HMRouterMgr.pop(); // pop 返回并携带参数 HMRouterMgr.pop({ param: { success: true } });3.2 跳转传参
发送方:
typescript
HMRouterMgr.push({ pageUrl: 'ProductDetail', param: { productId: '12345', title: '华为Mate 60' } });接收方:
typescript
@HMRouter({ pageUrl: 'ProductDetail' }) @Component export struct ProductDetail { @State productId: string = ''; @State title: string = ''; aboutToAppear(): void { const param = HMRouterMgr.getCurrentParam(); if (param) { this.productId = param.productId; this.title = param.title; } } }3.3 页面返回结果回调
跳转时注册onResult回调,接收返回页面的数据 :
typescript
HMRouterMgr.push({ pageUrl: 'EditProfile', param: { userId: '123' } }, { onResult: (popInfo: HMPopInfo) => { const fromPage = popInfo.srcPageInfo.name; const result = popInfo.result; console.log(`从 ${fromPage} 返回,数据:${JSON.stringify(result)}`); // 处理返回数据,如刷新页面 } });返回页发送数据:
typescript
// 在 EditProfile 页面中 HMRouterMgr.pop({ param: { updated: true, newName: '张三' } });3.4 返回指定页面
在复杂跳转链路中(如 A → B → C → D),可直接返回到指定页面并传参 :
typescript
// 从 D 页面直接返回到 A 页面 HMRouterMgr.pop({ navigationId: 'mainNavigation', pageUrl: 'HomePage', // 目标页面路径 param: { from: 'DetailPage', action: 'back' } });3.5 跨模块跳转
HMRouter 完美支持 HAR/HSP 模块间的跳转。假设cart模块需要跳转到entry模块的页面 :
1. 在 cart 模块的hvigorfile.ts中配置编译插件:
typescript
// cart/hvigorfile.ts import { hspTasks } from "@ohos/hvigor-ohos-plugin"; import { hspPlugin } from "@hadss/hmrouter-plugin"; export default { system: hspTasks, plugins: [hspPlugin()] };2. entry 模块依赖 cart 模块:
json
// entry/oh-package.json5 { "dependencies": { "cart": "file:../cart" } }3. 在 cart 模块中定义页面:
typescript
// cart/src/main/ets/views/CartDetail.ets @HMRouter({ pageUrl: 'CartDetail' }) @Component export struct CartDetail { // ... }4. entry 模块跳转:
typescript
HMRouterMgr.push({ pageUrl: 'CartDetail' });四、路由拦截器:登录校验与权限控制
拦截器是 HMRouter 最强大的功能之一,可在跳转前执行业务逻辑,常用于登录校验、权限验证、埋点统计等场景 。
4.1 定义拦截器
实现IHMInterceptor接口,使用@HMInterceptor注解 :
typescript
import { HMInterceptor, IHMInterceptor, HMInterceptorAction, HMInterceptorInfo } from '@hadss/hmrouter'; @HMInterceptor({ interceptorName: 'LoginCheckInterceptor' }) export class LoginCheckInterceptor implements IHMInterceptor { handle(info: HMInterceptorInfo): HMInterceptorAction { // 检查登录状态 const isLogin = AppStorage.get<boolean>('isLogin') ?? false; if (isLogin) { // 已登录:继续跳转 return HMInterceptorAction.DO_NEXT; } else { // 未登录:提示并跳转到登录页 info.context.getPromptAction().showToast({ message: '请先登录' }); HMRouterMgr.push({ pageUrl: 'LoginPage', skipAllInterceptor: true // 登录页跳过拦截,防止死循环 }); // 拒绝原始跳转 return HMInterceptorAction.DO_REJECT; } } }4.2 使用拦截器
局部拦截器(仅对指定页面生效):
typescript
@HMRouter({ pageUrl: 'ShoppingCart', interceptors: ['LoginCheckInterceptor'] // 数组形式,支持多个 }) @Component export struct ShoppingCart { // ... }全局拦截器(对所有跳转生效):
typescript
@HMInterceptor({ interceptorName: 'GlobalLoggerInterceptor', global: true, // 全局拦截器 priority: 10 // 优先级,数字越大越先执行 }) export class GlobalLoggerInterceptor implements IHMInterceptor { handle(info: HMInterceptorInfo): HMInterceptorAction { console.log(`[路由] 从 ${info.srcPage} 跳转到 ${info.targetName}`); return HMInterceptorAction.DO_NEXT; } }4.3 拦截器执行顺序
按
priority降序执行(数字越大越优先)同一优先级下,执行顺序为:发起页面拦截器 → 目标页面拦截器 → 全局拦截器
五、页面生命周期管理
HMRouter 提供了比系统更丰富的生命周期回调,通过@HMLifecycle注解定义生命周期处理器 。
5.1 定义生命周期
typescript
import { HMLifecycle, IHMLifecycle, HMLifecycleContext } from '@hadss/hmrouter'; @HMLifecycle({ lifecycleName: 'PageLifecycle' }) export class PageLifecycle implements IHMLifecycle { // 页面显示时触发 onShown(ctx: HMLifecycleContext): void { console.log('页面显示'); } // 页面隐藏时触发 onHidden(ctx: HMLifecycleContext): void { console.log('页面隐藏'); } // 返回键按下时触发(返回 true 表示拦截,false 表示执行默认返回) onBackPressed(ctx: HMLifecycleContext): boolean { console.log('用户按下返回键'); return false; // 执行默认返回 } // 页面即将出现 onWillAppear(ctx: HMLifecycleContext): void { console.log('页面即将出现'); } // 页面即将消失 onWillDisappear(ctx: HMLifecycleContext): void { console.log('页面即将消失'); } }5.2 绑定生命周期
typescript
@HMRouter({ pageUrl: 'HomePage', lifecycle: 'PageLifecycle' // 关联生命周期处理器 }) @Component export struct HomePage { // ... }5.3 典型场景:两次返回退出应用
typescript
@HMLifecycle({ lifecycleName: 'ExitAppLifecycle' }) export class ExitAppLifecycle implements IHMLifecycle { private lastTime: number = 0; onBackPressed(ctx: HMLifecycleContext): boolean { const now = new Date().getTime(); if (now - this.lastTime > 1000) { this.lastTime = now; ctx.uiContext.getPromptAction().showToast({ message: '再次返回退出应用' }); return true; // 拦截本次返回 } return false; // 执行返回,退出应用 } }在首页绑定该生命周期即可实现"双击返回退出"功能 。
六、自定义转场动画
6.1 定义页面动画
使用@HMAnimator注解定义动画类 :
typescript
import { HMAnimator, IHMAnimator, HMAnimatorHandle } from '@hadss/hmrouter'; @HMAnimator({ animatorName: 'SlideUpAnimator' }) export class SlideUpAnimator implements IHMAnimator { effect(enterHandle: HMAnimatorHandle, exitHandle: HMAnimatorHandle): void { // 入场动画:从底部滑入 enterHandle.start((translate: any) => { translate.y('100%').y(0); }, { duration: 300 }); // 出场动画:滑到底部 exitHandle.start((translate: any) => { translate.y(0).y('100%'); }, { duration: 300 }); } }6.2 使用动画
在目标页面的@HMRouter中指定动画 :
typescript
@HMRouter({ pageUrl: 'DetailPage', animator: 'SlideUpAnimator' // 使用自定义动画 }) @Component export struct DetailPage { // ... }6.3 全局默认动画
在HMNavigation的options中配置全局动画 :
typescript
HMNavigation({ navigationId: 'mainNavigation', options: { standardAnimator: HMDefaultGlobalAnimator.STANDARD_ANIMATOR, dialogAnimator: HMDefaultGlobalAnimator.DIALOG_ANIMATOR, // ... } })七、高级特性
7.1 单例页面
当页面初始化开销较大且需要频繁复用时(如视频播放页),可配置为单例模式 :
typescript
@HMRouter({ pageUrl: 'LivePlayer', singleton: true // 页面栈中只有一个实例 }) @Component export struct LivePlayer { // ... }7.2 Dialog 页面
将页面以弹窗形式展示 :
typescript
@HMRouter({ pageUrl: 'PrivacyDialog', dialog: true }) @Component export struct PrivacyDialog { build() { Stack({ alignContent: Alignment.Center }) { Column() { Text('隐私协议弹窗') // ... } .backgroundColor(Color.White) .borderRadius(16) .padding(20) } .width('100%') .height('100%') .backgroundColor('rgba(0,0,0,0.5)') } }7.3 指定页面扫描目录
默认扫描src/main/ets/components,可通过配置文件自定义 :
在项目根目录创建hmrouter_config.json:
json
{ "scanDir": ["src/main/ets/views", "src/main/ets/pages"] }八、最佳实践与常见问题
8.1 最佳实践
统一管理页面路径常量:
typescript
// constants/PageConstants.ets export class PageConstants { static readonly HOME = 'HomePage'; static readonly LOGIN = 'LoginPage'; static readonly PRODUCT_DETAIL = 'ProductDetail'; }合理使用拦截器层级:全局拦截器处理通用逻辑(埋点、日志),局部拦截器处理页面特定逻辑(表单校验)。
避免拦截器死循环:在拦截器跳转登录页时,务必设置
skipAllInterceptor: true。模块化开发:每个独立功能模块(HSP/HAR)独立配置编译插件,实现模块间解耦 。
8.2 常见问题
Q:跳转时提示页面未找到?
A:检查@HMRouter的pageUrl是否与跳转时一致,确认页面在扫描目录下(默认components),或配置scanDir。
Q:HAR 模块中的页面无法跳转?
A:确保 HAR 模块的hvigorfile.ts中配置了harPlugin(),并在宿主模块的oh-package.json5中依赖该 HAR 。
Q:如何调试路由跳转?
A:可配合系统NavPathStack的getAllPathName()方法查看当前页面栈。
九、参考资源
HMRouter Gitee 仓库
官方示例代码
接口文档
FAQ 常见问题
HMRouter 原理介绍
HMRouter 通过注解驱动和插件化编译的方式,极大地简化了 HarmonyOS 应用的路由管理。无论是基础的页面跳转、跨模块通信,还是复杂的拦截器、生命周期管理,HMRouter 都能提供优雅的解决方案。建议在新项目中直接采用 HMRouter,可以显著提升路由相关的开发效率。