多模块路由:实现组件化开发中的页面解耦跳转(48)
在鸿蒙(HarmonyOS)组件化开发中,实现多模块路由跳转是解决业务模块间强耦合的核心环节。根据官方架构设计和最佳实践,主要有以下几种主流方案:
方案一:基于 Navigation 的系统路由表(推荐)
这是鸿蒙官方推荐的跨包路由方案,利用 route_map.json 实现模块间解耦。
实现步骤:
- 在子模块(HAR/HSP)中配置路由表:在子模块的
resources/base/profile目录下创建route_map.json,定义页面名称、源文件路径和对外导出的 Builder 函数。
{
"routerMap": [
{
"name": "LoginPage",
"pageSourceFile": "src/main/ets/pages/LoginPage.ets",
"buildFunction": "LoginPageBuilder"
}
]
}
- 在
module.json5中注册路由表:
"module": {
"routerMap": "$profile:route_map"
}
- 在入口模块(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}`);
}
}
方案三:引入第三方路由框架(高阶解耦)
对于大型复杂项目,可以使用成熟的第三方路由框架(如 HMRouter、ZRouter),通过注解或编译插件自动生成路由表,进一步降低维护成本。
以 HMRouter 为例:
- 安装依赖:通过 ohpm 安装
@hadss/hmrouter核心库及编译插件@hadss/hmrouter-plugin。 - 配置编译插件:在工程根目录的
hvigor/hvigor-config.json5中配置插件依赖,并在hvigorfile.ts中启用appPlugin。 - 初始化与定义容器:在
UIAbility的onCreate中调用HMRouterMgr.init()初始化框架,并在首页使用HMNavigation作为路由容器。 - 注解声明与跳转:在目标页面使用
@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/router、ZRouter)的全局拦截器机制,我们可以在页面跳转前进行统一校验:
// 示例:基于 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 路由表,待核心页面(如首页、登录页)充分测试后再逐步迁移,确保线上版本的绝对稳定。
更多推荐




所有评论(0)