HarmonyOS 面试真题:组件化开发的原理和实现的方案?

150 阅读6分钟

我们在进行鸿蒙开发时如何进行组件化开发呢,接下来我将带大家了解鸿蒙开发中的组件化,项目的目录结构如下

其中features目录下是组件/模块,包含不同的功能分区,entity是项目的主入口也就是hap包,commons目录下有3个har组件,分别是utils:所有的帮助类、uicomponents:项目中需要用到的自定义UI组件等、RouterModule:项目的路由(承载了整个项目跨组件通信的能力)

接下来我们重点说一下RouterModule

不同组件之间想要通信,需要建立路由联系,RouterModule模块的实现主要包含以下步骤:

  1. 定义路由表和路由栈
export class RouterModule {
  // WrappedBuilder支持@Builder描述的组件以参数的形式进行封装存储
  static builderMap: Map<string, WrappedBuilder<[object]>> = new Map<string, WrappedBuilder<[object]>>();
  // 初始化路由栈,需要关联Navigation组件
  static navPathStack: NavPathStack = new NavPathStack();
}
  1. 路由表增加路由注册和路由获取方法,业务har模块通过路由注册方法将需要路由的页面组件委托给RouterModule管理,增加路由跳转方法,业务har模块通过调用该方法并指定跳转信息实现模块间路由跳转,完整代码如下
/**
 * @FileName : RouterModule
 * @Description : 路由管理
 */
import { RouterModel } from '../model/RouterModel';
import Logger from './Logger';
 
export class RouterModule {
  static builderMap: Map<string, WrappedBuilder<[object]>> = new Map<string, WrappedBuilder<[object]>>();
  static routerMap: Map<string, NavPathStack> = new Map<string, NavPathStack>();
 
  // Registering a builder by name.
  public static registerBuilder(builderName: string, builder: WrappedBuilder<[object]>): void {
    RouterModule.builderMap.set(builderName, builder);
  }
 
  // Get builder by name.
  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]>;
  }
 
  // Registering a router by name.
  public static createRouter(routerName: string, router: NavPathStack): void {
    RouterModule.routerMap.set(routerName, router);
  }
 
  // Get router by name.
  public static getRouter(routerName: string): NavPathStack {
    return RouterModule.routerMap.get(routerName) as NavPathStack;
  }
 
  // Jumping to a Specified Page by Obtaining the Page Stack.
  public static async push(router: RouterModel): Promise<void> {
    const harName = router.builderName.split('_')[0];
    // Dynamically import the page to be redirected to.
    await import(harName).then((ns: ESObject): Promise<void> => ns.harInit(router.builderName));
    RouterModule.getRouter(router.routerName).pushPath({ name: router.builderName, param: router.param });
  }
 
  // Obtain the page stack and pop it.
  public static pop(routerName: string): void {
    // Find the corresponding route stack for pop.
    RouterModule.getRouter(routerName).pop();
  }
 
  // Get the page stack and clear it.
  public static clear(routerName: string): void {
    // Find the corresponding route stack for pop.
    RouterModule.getRouter(routerName).clear();
  }
 
  // Directly jump to the specified route.
  public static popToName(routerName: string, builderName: string): void {
    RouterModule.getRouter(routerName).popToName(builderName);
  }
}

3.RouterModel为了方便在页面跳转直接进行传值,完整代码如下:

 * @FileName : RouterModel
 * @Description : 路由信息类,便于跳转时传递更多信息
 */
import { RouterModule } from '../utils/RouterModule';
 
export class RouterModel {
  // 路由页面别名
  builderName: string = "";
  routerName: string = "";
  // 需要传入页面的参数
  param?: string = "";
}
 
// 创建路由信息,并放到路由栈表中
export function buildRouterModel(routerName: string, builderName: string, param?: string) {
  let router: RouterModel = new RouterModel();
  router.builderName = builderName;
  router.routerName = routerName;
  router.param = param;
  RouterModule.push(router);
}

页面跳转实现

路由管理模块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"
}
  1. 在工程主入口模块Entry.hap中配置build-profile.json5文件,在该文件中修改packages字段,将需要进行路由注册的业务har模块写入配置。
{
  // ...
  "buildOption": {
    "arkOptions": {
      "runtimeOnly": {
        "sources": [
        ],
        "packages": [
          "@ohos/hara",
          "@ohos/harb",
          "@ohos/harc"
        ]
      }
    }
  },
}

DD一下:欢迎大家关注工粽号<程序猿百晓生>,可以了解到以下知识点。

`欢迎大家关注工粽号<程序猿百晓生>,可以了解到以下知识点。`
1.OpenHarmony开发基础
2.OpenHarmony北向开发环境搭建
3.鸿蒙南向开发环境的搭建
4.鸿蒙生态应用开发白皮书V2.0 & V3.0
5.鸿蒙开发面试真题(含参考答案) 
6.TypeScript入门学习手册
7.OpenHarmony 经典面试题(含参考答案)
8.OpenHarmony设备开发入门【最新版】
9.沉浸式剖析OpenHarmony源代码
10.系统定制指南
11.【OpenHarmony】Uboot 驱动加载流程
12.OpenHarmony构建系统--GN与子系统、部件、模块详解
13.ohos开机init启动流程
14.鸿蒙版性能优化指南
.......
  1. 在工程主入口模块的首页Navigation组件上关联RouterModule模块的路由栈和路由表。其中RouterModule.createRouter()与RouterModule.getBuilder()方法的实现。
@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);
  }
}
  1. 在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);
}
  1. 在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模块的页面时序图如下:

**图5 **entry模块页面上点击跳转到harA模块的时序图

注意 上述内容主要介绍了HAR包,若开发者使用了HSP包或者混合使用了HAR与HSP,都需要按如下代码,配置项目根目录下的build-profile.json5文件。

{
  "app": {
    "signingConfigs": [],
    "products": [
      {
        // ...
        "buildOption": {
          "strictMode": {
            "useNormalizedOHMUrl": true
          }
        }
      }
    ],
    // ...
  },
  // ...
}

具体实现

在harMine的对外导出类Index.ets中定义加载时的初始化函数harInit,该函数对harMine中需要注册路由的页面组件进行加载管理,被调用时将根据不同的路径动态加载不同的页面。

import { BuilderNameConstants } from '@ohos/routermodule';
 
export { MinePage } from './src/main/ets/components/mainpage/MinePage'
 
export function harInit(builderName: string): void {
  // 动态引入要跳转的页面
  switch (builderName) {
    case BuilderNameConstants.MINE_ORDERLIST:
      import("./src/main/ets/components/mainpage/OrderListPage");
      break;
    case BuilderNameConstants.MINE_LOGIN:
      import("./src/main/ets/components/mainpage/LoginPage");
      break;
    case BuilderNameConstants.MINE_TEST:
      import("./src/main/ets/components/mainpage/test");
      break;
    default:
      break;
  }
}

最后在Hap工程的build-profile.jsn5中添加所有的动态库

  "buildOption": {
    "arkOptions": {
      "runtimeOnly": {
        "sources": [
        ],
        "packages": [
          "@ohos/home",
          "@ohos/report",
          "@ohos/shopcart",
          "@ohos/mine",
          "@ohos/utils",
          "@ohos/routermodule"
        ]
      }
    }
  },

接下来在点击主页的登录按钮,就能实现从主页跳转到登录页,返回后依然能返回到前面的页面。