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 拦截器执行顺序

  1. 按 priority 降序执行(数字越大越优先)

  2. 同一优先级下,执行顺序为:发起页面拦截器 → 目标页面拦截器 → 全局拦截器 


五、页面生命周期管理

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 最佳实践

  1. 统一管理页面路径常量

typescript

// constants/PageConstants.ets
export class PageConstants {
  static readonly HOME = 'HomePage';
  static readonly LOGIN = 'LoginPage';
  static readonly PRODUCT_DETAIL = 'ProductDetail';
}
  1. 合理使用拦截器层级:全局拦截器处理通用逻辑(埋点、日志),局部拦截器处理页面特定逻辑(表单校验)。

  2. 避免拦截器死循环:在拦截器跳转登录页时,务必设置 skipAllInterceptor: true 。

  3. 模块化开发:每个独立功能模块(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 通过注解驱动和插件化编译的方式,极大地简化了 HarmonyOS 应用的路由管理。无论是基础的页面跳转、跨模块通信,还是复杂的拦截器、生命周期管理,HMRouter 都能提供优雅的解决方案。建议在新项目中直接采用 HMRouter,可以显著提升路由相关的开发效率。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐