在鸿蒙(HarmonyOS)组件化开发中,实现多模块路由跳转是解决业务模块间强耦合的核心环节。根据官方架构设计和最佳实践,主要有以下几种主流方案:

方案一:基于 Navigation 的系统路由表(推荐)

这是鸿蒙官方推荐的跨包路由方案,利用 route_map.json 实现模块间解耦。

实现步骤:

  1. 在子模块(HAR/HSP)中配置路由表:在子模块的 resources/base/profile 目录下创建 route_map.json,定义页面名称、源文件路径和对外导出的 Builder 函数。
{
  "routerMap": [
    {
      "name": "LoginPage",
      "pageSourceFile": "src/main/ets/pages/LoginPage.ets",
      "buildFunction": "LoginPageBuilder"
    }
  ]
}
  1. 在 module.json5 中注册路由表
"module": {
  "routerMap": "$profile:route_map"
}
  1. 在入口模块(HAP)中跳转:主模块无需直接依赖子模块的页面代码,只需通过 NavPathStack 的 pushPathByName 方法,使用路由表中的 name 即可实现跳转。

方案二:基于 Router 的命名路由

适用于传统的 @ohos.router 体系。在子模块的页面组件上使用 @Entry({ routeName: 'CustomPage' }) 进行命名,主模块通过 router.pushNamedRoute({ name: 'CustomPage' }) 进行跳转。

1. 目标模块(共享包):声明命名路由

在需要被跳转的共享包(如 library)中,给 @Entry 修饰的自定义组件配置 routeName

// library/src/main/ets/pages/DetailPage.ets
import { router } from '@kit.ArkUI';

// 1. 使用 @Entry 的 routeName 属性定义路由别名
@Entry({ routeName: 'LibraryDetailPage' }) 
@Component
export struct DetailPage {
  build() {
    Column() {
      Text('共享包详情页')
        .fontSize(24)
      Button('返回并传参')
        .onClick(() => {
          // 返回上一页,并携带自定义参数
          router.back({
            url: 'pages/Index', // 返回主模块的首页
            params: { message: '操作成功' }
          });
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

2. 主模块(HAP):配置依赖与跳转

在主模块中,首先需要确保在 oh-package.json5 中引入了该共享包,然后导入目标页面文件并执行跳转。

// entry/src/main/ets/pages/Index.ets
import { router } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';

// 2. 必须引入共享包中定义了命名路由的页面文件
import 'library/src/main/ets/pages/DetailPage'; 

@Entry
@Component
struct Index {
  build() {
    Column() {
      Button('跳转到共享包页面')
        .onClick(() => {
          try {
            // 3. 使用 pushNamedRoute 通过别名进行跳转
            router.pushNamedRoute({
              name: 'LibraryDetailPage', // 对应目标页面的 routeName
              params: {
                userId: '12345',
                data: { age: 20 }
              }
            });
          } catch (err) {
            let error = err as BusinessError;
            console.error(`路由跳转失败,code: ${error.code}, message: ${error.message}`);
          }
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

3. 目标模块:接收参数

在目标页面中,可以通过生命周期(如 onPageShow)获取主模块传递过来的参数。

// 在 DetailPage.ets 的组件内部
onPageShow() {
  // 4. 获取传递过来的参数对象
  const params = router.getParams() as Record<string, Object>;
  if (params) {
    const userId = params['userId'] as string;
    console.info(`接收到主模块传递的用户ID: ${userId}`);
  }
}

方案三:引入第三方路由框架(高阶解耦)

对于大型复杂项目,可以使用成熟的第三方路由框架(如 HMRouterZRouter),通过注解或编译插件自动生成路由表,进一步降低维护成本。

以 HMRouter 为例:

  1. 安装依赖:通过 ohpm 安装 @hadss/hmrouter 核心库及编译插件 @hadss/hmrouter-plugin
  2. 配置编译插件:在工程根目录的 hvigor/hvigor-config.json5 中配置插件依赖,并在 hvigorfile.ts 中启用 appPlugin
  3. 初始化与定义容器:在 UIAbility 的 onCreate 中调用 HMRouterMgr.init() 初始化框架,并在首页使用 HMNavigation 作为路由容器。
  4. 注解声明与跳转:在目标页面使用 @HMRouter({ pageUrl: 'PageB' }) 注解声明路由信息,跳转时直接通过 HMRouterMgr.push('PageB') 即可完成跨模块通信,彻底告别硬编码依赖。

1. 安装依赖与配置编译插件

首先,在工程的依赖配置文件中引入核心库,并在构建脚本中启用编译插件。

// oh-package.json5
{
  "dependencies": {
    "@hadss/hmrouter": "^1.0.0" // 引入核心运行时库
  }
}
// 工程根目录 hvigorfile.ts
import { appTasks } from '@ohos/hvigor-ohos-plugin';
import { appPlugin } from "@hadss/hmrouter-plugin"; // 引入编译插件

export default {
    system: appTasks,
    plugins: [appPlugin()] // 启用插件,编译时自动扫描注解并生成路由表
};

2. 框架初始化与根容器配置

在应用启动时初始化框架,并在首页配置 HMNavigation 作为全局路由栈容器。

// 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 });
    }
}
// pages/Index.ets (首页)
import { HMNavigation } from '@hadss/hmrouter';

@Entry
@Component
struct Index {
    build() {
        Column() {
            // 使用 HMNavigation 作为路由根容器
            HMNavigation({
                navigationId: 'MainNavigation', // 导航栈唯一标识
                homePageUrl: 'HomePage'         // 首页路由标识
            })
        }
        .height('100%')
        .width('100%')
    }
}

3. 注解声明页面与跨模块跳转

在目标页面使用 @HMRouter 注解声明路由,跳转时只需传递字符串标识,无需 import 目标页面的代码。

目标页面(如位于子模块 ModuleB):

// ModuleB/src/main/ets/pages/DetailPage.ets
import { HMRouter, HMRouterMgr } from '@hadss/hmrouter';

@HMRouter({ 
    pageUrl: 'ProductDetail' // 声明唯一的路由标识
})
@Component
export struct DetailPage {
    build() {
        Column() {
            Text('商品详情页')
            Button('返回并传参')
                .onClick(() => {
                    // 返回上一页并携带数据
                    HMRouterMgr.pop({ 
                        param: { action: 'refresh_list' } 
                    });
                })
        }
    }
}

发起跳转的页面(如位于主模块 ModuleA):

// ModuleA/src/main/ets/pages/HomePage.ets
import { HMRouterMgr } from '@hadss/hmrouter';

@HMRouter({ pageUrl: 'HomePage' })
@Component
export struct HomePage {
    build() {
        Column() {
            Button('跳转到商品详情')
                .onClick(() => {
                    // 跨模块跳转,无需 import DetailPage
                    HMRouterMgr.push({
                        pageUrl: 'ProductDetail', // 匹配目标页面的 pageUrl
                        param: { 
                            productId: '10086', 
                            source: 'home' 
                        }
                    });
                })
        }
    }
}

4. 目标页面接收参数

在目标页面中,可以通过 HMRouterMgr.getCurrentParam() 安全地获取跳转时传递的参数:

// 在 DetailPage.ets 中
aboutToAppear() {
    // 获取传递过来的参数
    const params = HMRouterMgr.getCurrentParam() as Record<string, Object>;
    if (params) {
        const productId = params['productId'] as string;
        console.info(`接收到商品ID: ${productId}`);
    }
}

一、 路由拦截与全局鉴权(Navigation Guards)

在大型应用中,很多页面(如订单详情、个人中心)需要登录态或特定权限。如果每个页面都去写鉴权逻辑,会导致严重的代码冗余。我们需要在路由层实现全局拦截。

借助鸿蒙原生的 NavPathStack 拦截器,或者第三方框架(如 @fw/routerZRouter)的全局拦截器机制,我们可以在页面跳转前进行统一校验:

// 示例:基于 NavPathStack 的路由拦截器
const pathStack = new NavPathStack();

pathStack.setInterception({
  willShow: (navDestinationContext: NavDestinationContext) => {
    const targetRoute = navDestinationContext.pathInfo.name;
    
    // 定义需要鉴权的路由白名单/黑名单
    const requireAuthRoutes = ['OrderDetail', 'UserProfile'];
    
    if (requireAuthRoutes.includes(targetRoute)) {
      const isLoggedIn = AppStorage.get<boolean>('isLoggedIn') ?? false;
      if (!isLoggedIn) {
        // 拦截跳转,重定向到登录页,并携带原始目标路由作为参数
        pathStack.pushPathByName('LoginPage', { redirect: targetRoute });
        return false; // 阻止当前跳转
      }
    }
    return true; // 允许跳转
  }
});

二、 跨模块通信与状态共享

路由解耦后,模块 A 跳转到模块 B,如果模块 B 需要模块 A 的数据,或者模块 B 执行完操作后需要通知模块 A 刷新,直接互相 import 会破坏组件化架构。

1. 参数传递与返回值
利用 NavPathStack 的 pushPath 传递参数,并结合 pop() 的返回值实现跨模块通信:

// 模块 A:发起跳转并接收返回值
pathStack.pushPathByName('ModuleB_Page', { itemId: '123' }).then((result) => {
  if (result === 'refresh_needed') {
    this.refreshList(); // 模块 B 操作完成后,刷新模块 A 的列表
  }
});

// 模块 B:处理完毕并返回结果
this.pathStack.pop({ action: 'refresh_needed' });

2. 全局状态管理
对于跨模块的共享数据(如用户信息、全局配置),应摒弃路由参数传递,统一使用鸿蒙的 AppStorage 或 PersistentStorage

// 任何模块均可读写
AppStorage.setOrCreate('global_user_info', userInfo);

三、 路由性能优化与懒加载

当应用包含数十个甚至上百个分包(HAR/HSP)时,启动时加载所有路由表和页面资源会导致严重的性能瓶颈。

1. 页面级懒加载(Lazy Loading)
结合 ArkUI 的声明式渲染特性,对于 Tab 页面或复杂的嵌套路由,使用 if-else 条件渲染实现按需加载,避免一次性创建所有组件实例:

build() {
  Column() {
    if (this.currentTabIndex === 0) {
      HomeTabContent()
    } else if (this.currentTabIndex === 1) {
      // 只有切换到发现页时,才真正加载该模块的 UI 树
      DiscoverTabContent() 
    }
  }
}

2. 动态导入与按需加载
对于超大型应用,可以使用动态 import() 语法,在真正触发路由跳转时才去加载对应的 HAR/HSP 模块,从而极大缩短冷启动时间。

四、 渐进式迁移策略(从 Router 到 Navigation)

如果是一个历史包袱较重的老项目,正在从传统的 @ohos.router 向 Navigation 体系迁移,切忌“一刀切”式重构。推荐采用兼容层(Adapter)模式

// 封装统一的路由分发器
export function smartNavigate(url: string, params?: object) {
  if (isNavigationMode) {
    // 新架构:使用 NavPathStack
    navPathStack.pushPathByName(url, params);
  } else {
    // 老架构:使用传统 Router
    router.pushUrl({ url: url, params: params });
  }
}

通过这种策略,可以优先将非核心、低风险的新增页面接入 Navigation 路由表,待核心页面(如首页、登录页)充分测试后再逐步迁移,确保线上版本的绝对稳定。

Logo

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

更多推荐