应用导航设计

目录

场景描述

基本解决方案

推荐方案

路由管理模块实现 

 页面跳转实现


大型应用开发中,应用可能包含不同的业务模块,每个模块由不同的业务团队负责开发。该场景采用一个Navigation下多个har/hsp的架构,其中一个模块对应一个har/hsp。当多个har/hsp的UI组件存在相互跳转的业务需求时,将出现模块间相互依赖的问题。如“A.har”、“B.har”和“C.har”模块拥有不同的组件,各组件间的路由跳转形成了一个环形链路,导致三个har模块相互耦合,如图所示:

场景描述

假定工程包含harA和harB两个业务模块,harA模块打包编译为A.har,harB模块打包编译为B.har。A.har归属团队A独立开发、编译交付。B.har归属团队B独立开发、编译交付。harA模块中含有页面级组件A1,harB模块中有页面级组件B1、B2、B3。在实际业务中,harA模块中的A1组件需要跳转到harB模块中的B1组件,项目关系如下图所示。例如购物场景,商品选购和结算支付是两个独立的模块,用户在选购完成后,需要进入结算页面进行地址填写和付款等操作。

 

基本解决方案

基本方案实现步骤如下:

1.在harA模块中的A1页面组件中开发Navigation组件,并关联与之对应的NavPathStack路由栈,示例代码如下:

@Component
struct A1 {
  // 创建NavPathStack路由栈对象
  @State harARouter: NavPathStack = new NavPathStack();

  build() {
    // Navigation组件关联NavPathStack对象
    Navigation(this.harARouter) {
      // ...
    }
  }
}

2.在harA模块的oh-package.json5文件中添加harB模块的依赖,并且把harB模块中需要跳转的B1组件添加到harA模块的Navigation组件路由表中,示例代码如下 :

"dependencies": {
  // 添加对harB的依赖
  "@ohos/harb": "file:../harB"
}

在harA模块的A1组件中的routerMap路由表中,添加harB模块的B1组件,示例代码如下:

import { B1 } from '@ohos/harb';
struct A1 {
  @State harARouter: NavPathStack = new NavPathStack();

  @Builder
  routerMap(builderName: string, param: object) {
    if (builderName === 'B1') {
      B1() // 在routerMap中添加需要跳转的harB模块的B1页面
    }
  }
  
  build() {
    Navigation(this.harARouter) {
      // ...
    }
    .navDestination(this.routerMap) // Navigation关联上routerMap路由表
  }
}

3.在harA模块的Navigation组件中添加跳转到harB模块的B1页面的逻辑,完整示例代码如下:

import { B1 } from '@ohos/harb';

struct A1 {
  // 创建NavPathStack路由栈
  @State harARouter: NavPathStack = new NavPathStack();

  @Builder
  routerMap(builderName: string, param: object) {
    if (builderName === 'B1') {
      B1() // 在routerMap中添加需要跳转的harB模块的B1页面
    }
  }

  build() {
    // Navigation关联NavPathStack对象
    Navigation(this.harARouter) {
      Button('跳转到HarB的B1页面')
        .onClick(() => {
          // 跳转到已在路由表注册的harB模块的B1页面
          this.harARouter.pushPathByName('B1', null);
        })
    }
    .navDestination(this.routerMap) // Navigation关联上routerMap路由表
  }
}

但基本方案存在以下问题:

  • 使用Navigation时,所有路由页面需要主动通过import方式逐个导入当前页面,并存入页面路由表routerMap中。
  • 主动使用import的方式需显性指定加载路径,造成开发态模块耦合严重。
  • 模块无法独立编译,且存在开发态模块间循环依赖问题。

因此推荐如下方案:

推荐方案

将路由功能抽取成单独的模块并以har包形式存在,命名为RouterModule。RouterModule内部对路由进行管理,对外暴露RouterModule对象供其他模块使用。Entry.hap是应用必备的主入口,利用该特性将主入口模块作为其他业务模块的依赖注册中心,在入口模块中使用Navigation组件并依赖其他业务模块。业务模块仅依赖RouterModule,业务模块中的路由统一委托到RouterModule中管理,实现业务模块间的解耦。按照推荐方案,上述场景各模块依赖关系如下:

此方案中,各模块的依赖关系如下:

  • Entry.hap、A.har和B.har均依赖了RouterModule.har;
  • Entry.hap在工程配置中依赖了A.har和B.har;
  • 对于业务开发团队之间,A.har在工程和源码上无需依赖B.har的库,实现了业务模块间的解耦。

路由管理模块实现 

RouterModule模块包含全局的路由栈和路由表信息。路由栈是NavPathStack对象,该对象与Entry.hap的Navigation组件绑定,RouterModule通过持有NavPathStack管理Navigation组件的路由信息。路由表builderMap是Map结构,以key-vaule的形式存储了需要路由的页面组件信息,其中key是自定义的唯一路由名,value是WrappedBuilder对象,该对象包裹了路由名对应的页面组件。RouterModule模块结构如下:

RouterModule模块的实现主要包含以下步骤:

1. 定义路由表和路由栈。

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>();

  // ...
}

2. 路由表增加路由注册和路由获取方法,业务har模块通过路由注册方法将需要路由的页面组件委托给RouterModule管理。

// 通过名称注册路由栈
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 ' + builderName);
  }
  return builder as WrappedBuilder<[object]>;
}

3.路由表增加路由跳转方法,业务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 });
}

 页面跳转实现

路由管理模块RouterModule实现之后,需要使用RouterModule模块实现业务模块harA的页面跳转到业务模块harB的页面功能。主要步骤如下:

1.在工程主入口模块Entry.hap中引入RouterModule模块和所有需要进行路由注册的业务har模块。

"dependencies": {
  "@ohos/routermodule": "file:../RouterModule",
  "@ohos/hara": "file:../harA",
  "@ohos/harb": "file:../harB",
  "@ohos/harc": "file:../harC"
}

2.在工程主入口模块Entry.hap中配置build-profile.json5文件,在该文件中修改packages字段,将需要进行路由注册的业务har模块写入配置。

{
  // ...
  "buildOption": {
    "arkOptions": {
      "runtimeOnly": {
        "sources": [
        ],
        "packages": [
          "@ohos/hara",
          "@ohos/harb",
          "@ohos/harc"
        ]
      }
    }
  },
}

3.在工程主入口模块的首页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);
  };

  @Builder
  routerMap(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);
  }
}

4.在harB中声明需要跳转的页面,并且调用registerBuilder接口将页面注册到RouterModule模块的全局路由表上。以下注册逻辑会在harB的B1页面被首次加载时触发。

// harB模块的B1页面
@Builder
export function harBuilder(value: object) {
  NavDestination() {
    Column() {
      // ...
    }
    // ...
  }
  // ...
}

// 在页面首次加载时触发执行
const builderName = BuilderNameConstants.HARB_B1;
// 判断表中是否已存在路由信息,避免重复注册
if (!RouterModule.getBuilder(builderName)) {
  // 通过系统提供的wrapBuilder接口封装@Builder装饰的方法,生成harB1页面builder
  let builder: WrappedBuilder<[object]> = wrapBuilder(harBuilder);
  // 注册harB1页面到全局路由表
  RouterModule.registerBuilder(builderName, builder);
}
5.在harA模块中的A1页面调用RouterModule模块的push方法实现跳转到harB的B1页面。当harB的B1页面被首次通过push方法跳转时,会动态加载B1页面,并且触发步骤4中B1页面的路由注册逻辑,把B1页面注册到RouterModule的全局路由表builderMap中。
@Builder
export function harBuilder(value: object) {
  NavDestination() {
    Column() {
      // ...
      Button($r("app.string.to_harb_pageB1"), { stateEffect: true, type: ButtonType.Capsule })
        .width('80%')
        .height(40)
        .margin(20)
        .onClick(() => {
          buildRouterModel(RouterNameConstants.ENTRY_HAP, BuilderNameConstants.HARB_B1);
        })
    }
    .width('100%')
    .height('100%')
  }
  .title('A1Page')
  .onBackPressed(() => {
    RouterModule.pop(RouterNameConstants.ENTRY_HAP);
    return true;
  })
}

上述方案,当在entry模块页面上点击跳转到harA模块的页面时序图如下:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值