大家好,我是小z,不知道大家在开发过程中有没有遇到模块间跳转的问题,今天给大家分享关于模块间跳转的三种方法
文章目录
- 1. 命名路由(@ohos.router)
- 使用步骤
- 2. 使用navigation组件跳转。
- 步骤
- 缺点
- 3. 路由管理模块
- 1. 路由管理模块实现
- 2. 实现中的关键点
- 动态加载
- 路由栈管理
- 3. 页面跳转实现
- 主要步骤
- 大型应用开发中,应用会分为多个模块进行开发,单个业务模块是一个HAR或一个HSP。在该场景下,通常会有从主页面跳转到其他模块页面或一个模块跳转到另一个模块的需求。针对这些需求,有三种解决办法,推荐第三种
1. 命名路由(@ohos.router)
- 在开发中为了跳转到共享的HAR或HSP,可以使用router.pushNameRoute来实现
使用步骤
-
导入Router模块
import { router } from '@kit.ArkUI';
-
给想要跳转到的模块中,给@Entry修饰的自定义组件命名
// library/src/main/ets/pages/Index.ets // library是子模块 @Entry({ routeName: 'myPage' }) @Component export struct MyComponent {build() {} }
-
在进行跳转的模块或HAP中的oh-package.json5文件中配置依赖
"dependencies": {"@ohos/library": "file:../library",... }
-
配置成功后需要在需要进行跳转的页面中引入命名路由的页面:
import { BusinessError } from '@kit.BasicServicesKit'; import '@ohos/library/src/main/ets/pages/Index'; // 引入共享包中的命名路由页面@Entry @Component struct Index {build() {Text('Hello World').fontSize(50).fontWeight(FontWeight.Bold).margin({ top: 20 }).backgroundColor('#ccc').onClick(() => { // 点击跳转到其他共享包中的页面this.getUIContext().getRouter().pushNamedRoute({name: 'myPage',params: {data1: 'message',data2: {data3: [123, 456, 789]}}})})} }
2. 使用navigation组件跳转。
- 以从应用入口模块的页面NavigationPage跳转到Login子业务模块页面LoginPage为例。
步骤
-
在Login模块中开发自定义组件LoginPage(路由跳转目的地),并对外导出。
@Component export struct LoginPage { @Consume('pathStack') pathStack: NavPathStack; @State message: string = 'Login Page'; build() { NavDestination() { Column() { Text(this.message) .fontSize(50) .fontWeight(FontWeight.Bold) } .width('100%') .height('100%') } .onBackPressed(() => { this.pathStack.pop(); return true; }) } }
-
在Login模块的入口文件Index.ets中导出自定义组件。
export { LoginPage } from './src/main/ets/pages/loginPage';
-
在入口模块的oh-package.json5文件中添加对Login模块的依赖。
{ // ... "dependencies": { "login": "file:../login" } }
-
入口模块LoginPage页面导入Login模块的自定义组件,并添加到Navigation组件的路由表中
// 导入Login模块自定义组件 import { LoginPage } from '@ohos/login'; @Entry @Component struct NavigationPage { @Provide('pathStack') pathStack: NavPathStack = new NavPathStack(); @Builder pageMap(name: string) { if (name === 'loginPage') { LoginPage() } } build() { Navigation(this.pathStack) { Button('jump to login page') .onClick(() => { // NavPathInfo第二个参数为自定义参数,可用于信息传递 let pathInfo: NavPathInfo = new NavPathInfo('loginPage', new Object()); this.pathStack.pushDestination(pathInfo, true); }) } .navDestination(this.pageMap) } }
缺点
上述方案存在以下问题:
- 使用Navigation时,所有路由页面需要主动通过import方式逐个导入当前页面,并存入页面路由表routerMap中。
- 主动使用import的方式需显性指定加载路径,造成开发态模块耦合严重。
- 模块无法独立编译,且存在开发态模块间循环依赖问题。
所以推荐使用方案3,路由管理模块
3. 路由管理模块
- 将路由功能抽取成单独的模块并以har包形式存在,命名为RouterModule。将主入口模块作为其他模块的依赖注册中心,在入口模块中使用Navigation组件并依赖其他业务模块。业务模块仅依赖RouterModule,业务模块中的路由统一委托到RouterModule中管理,实现业务模块间的解耦。
1. 路由管理模块实现
-
RouterModule模块包括全局路由栈(NavPathStack)和路由表信息。路由栈和Entry.hap的Navigation绑定。路由表builderMap是Map结构,以key-vaule的形式存储了需要路由的页面组件信息,其中key是自定义的唯一路由名,value是WrappedBuilder对象,该对象包裹了路由名对应的页面组件。
-
定义路由栈和路由表
export class RouterModule {// WrappedBuilder支持@Builder描述的组件以参数的形式进行封装存储static builderMap: Map<string, WrappedBuilder<[object]>> = new Map<string, WrappedBuilder<[object]>>();// 初始化路由栈,需要关联Navigation组件static routerMap: Map<string, NavPathStack> = new Map<string, NavPathStack>();// 通过名称获取路由栈public static getRouter(routerName: string): NavPathStack {return RouterModule.routerMap.get(routerName) as NavPathStack;}// 通过传入RouterModule跳转到指定页面组件,RouterModule中需要增加routerName字段用于获取路由栈public static async push(router: RouterModel): Promise<void> {const harName = router.builderName.split('_')[0];await import(harName).then((ns: ESObject): Promise<void> => ns.harInit(router.builderName));RouterModule.getRouter(router.routerName).pushPath({ name: router.builderName, param: router.param });}//... }
-
路由表增加路由注册和路由获取方法,业务模块通过路由注册方法将路由的页面组件交给RouteModule管理
//通过名称注册路由表 public static registerBuilder(builderName: string, builder: WrappedBuilder<[object]>):void{RouterModule.builderMap.set(builderName, builder); } // 获取路由表中指定的页面组件 public static getBuilder(builderName: string): WrappedBuilder<[object]>{const builder = RouterModule.builderMap.get(builderName);if(!builder){Logger.info('not found builder ' + buiderName);}return builder as WrappedBuilder<[object]>; }
-
路由表增加路由跳转方法,业务har模块通过调用该方法并指定跳转信息实现模块间路由跳转。
public static async push(router: RouterModel): Promise<void> {const harName = router.builderName.split('_')[0];await import(harName).then((ns: ESObject): Promise<void> => ns.harInit(router.builderName));RouterModule.getRouter(router.routerName).pushPath({ name: router.builderName, param: router.param }); }//其他路由方法... public static async replacePath(routerName: string, builderName: string, params: object): Promise<void> {const harName = builderName.split('_')[0];await import(harName).then((ns: ESObject): Promise<void> => ns.harInit(builderName));RouterModule.getRouter(routerName).replacePathByName(builderName, params);}
RouterModel如下,harInit是实现动态加载的函数,帮助模块解耦
// 路由信息类,便于跳转时传递更多信息 export class RouterModel {// 路由页面别名,形式为${包名}_${页面名}builderName: string = "";// 路由栈名称routerName: string = "";// 需要传入页面的参数param?: object = new Object(); }// 创建路由信息,并放到路由栈表中 export function buildRouterModel(routerName: string, builderName: string, param?: object) {let router: RouterModel = new RouterModel();router.builderName = builderName;router.routerName = routerName;router.param = param;RouterModule.push(router); }
2. 实现中的关键点
动态加载
实现解耦的关键是使用了动态加载的方式和自执行的函数(自执行的函数是一种在定义后立即自动执行的函数)。要跳转到哪一个模块的页面,需要在该模块的index.ets文件中定义加载时的harInit- harInit函数对模块中需要注册路由的页面组件进行加载管理,被调用时将根据不同的路径动态加载不同的页面
export function harInit(builderName: string): void {// 根据routerModule中路由表的key值动态加载要跳转的页面的相对路径switch (builderName) {case BuilderNameConstants.HARB_B1:import("./src/main/ets/components/mainpage/B1");break;case BuilderNameConstants.HARB_B2:import("./src/main/ets/components/mainpage/B2");break;default:break;}
}
路由栈管理
有些业务场景需要用到多个路由栈,所以在RouterModule中,可以使用Map,以key-value形式来实现存储多个路由栈。增加路由栈后,RouterModule中的所有方法都需要先获取到相应的路由栈,再进行方法调用。
export class RouterModule {// ...// 初始化路由栈,需要关联Navigation组件static routerMap: Map<string, NavPathStack> = new Map<string, NavPathStack>();// 通过名称注册路由栈public static createRouter(routerName: string, router: NavPathStack): void {RouterModule.routerMap.set(routerName, router);}// 通过名称获取路由栈public static getRouter(routerName: string): NavPathStack {return RouterModule.routerMap.get(routerName) as NavPathStack;}// ...
}
3. 页面跳转实现
路由管理模块实现之后,需要使用RouterModule模块实现不同模块之间的跳转(如harA到harB)
主要步骤
-
在工程主入口模块Entry.hap中引入RouterModule模块和所有需要进行路由注册的业务har模块。
"dependencies": {"@ohos/routermodule": "file:../RouterModule","@ohos/hara": "file:../harA","@ohos/harb": "file:../harB" }
-
在工程主入口模块Entry.hap中配置build-profile.json5文件,在该文件中修改packages字段,将需要进行路由注册的业务har模块写入配置。
{// ..."buildOption": {"arkOptions": {"runtimeOnly": {"sources": [],"packages": ["@ohos/hara","@ohos/harb"]}}}, }
-
在工程主入口模块的首页Navigation组件关联RouterModule模块的路由栈和路由表。
@Entry @Component struct EntryHap {@State entryHapRouter: NavPathStack = new NavPathStack();aboutToAppear() {if (!this.entryHapRouter) {this.entryHapRouter = new NavPathStack();}RouterModule.createRouter(RouterNameConstants.ENTRY_HAP, this.entryHapRouter);};@BuilderrouterMap(builderName: string, param: object) {// Obtain the WrappedBuilder object based on the module name, create a page through the builder interface, and import the param parameter.RouterModule.getBuilder(builderName).builder(param);};build() {Navigation(this.entryHapRouter) {// ...}.title('NavIndex').navDestination(this.routerMap);} }
-
在跳转到的页面中(harB)中,声明需要跳转的页面,并且调用registerBuilder接口将页面注册到RouterModule模块的全局路由表上。如果组件中需要使用状态变量,可以将Builder函数中的组件作为一个自定义组件。
// harB模块的B1页面 @Builder export function harBuilder(value: object) {NavDestination() {Column() {// ...}// ...}// ... }@Builder export function harBuilder(value: object) {HarBPage({value:value}); }// 在页面首次加载时触发执行 const builderName = BuilderNameConstants.HARB_B1; // 判断表中是否已存在路由信息,避免重复注册 if (!RouterModule.getBuilder(builderName)) {// 通过系统提供的wrapBuilder接口封装@Builder装饰的方法,生成harB1页面builderlet builder: WrappedBuilder<[object]> = wrapBuilder(harBuilder);// 注册harB1页面到全局路由表RouterModule.registerBuilder(builderName, builder); }