众所周知,现在组件化在移动开发中是很常见的,那么组件化有哪些好处:
-
提高代码复用性:组件化允许将应用程序的不同功能模块化,使得这些模块可以在不同的项目中重复使用,从而提高开发效率并减少重复工作。
-
降低组件间的耦合:通过组件化的规则将代码拆分成不同的模块,实现高内聚、低耦合,使得代码更易于维护,降低了模块间的依赖,减少了潜在的错误和问题。
-
提升开发效率:组件化使得开发团队可以并行工作,每个团队可以专注于自己的组件,独立开发和维护,这样可以加快开发进度,提高整体的开发效率。
-
改善代码质量:组件化鼓励开发者编写清晰、模块化的代码,有助于提高代码的可读性和可维护性,从而提升代码质量。
-
便于扩展和迭代:组件化架构使得添加新功能或改进现有功能变得更加容易,有助于快速响应市场变化和用户需求。
-
隔离技术栈:不同的组件可以使用不同的技术栈,而不会相互影响,使得技术选型更加灵活。
-
独立开发/维护/发布:组件化允许每个组件独立开发、维护和发布,使得更新和迭代更加灵活。
-
提高编译/构建速度:组件化使得编译和构建过程更加高效,因为只需要编译和构建相关的组件,而不是整个项目。
-
管控代码权限:组件化允许更好地控制代码权限,通过将代码分散到不同的仓库中,可以限制对特定组件的访问和修改。
-
管理版本变更:组件化使得管理版本变更变得更加容易,因为每个组件都有明确的版本,可以更容易地跟踪和控制版本更新。
组件化是解决单一工程架构开发中问题的有效方法,它通过将大型项目拆分成更小、更易于管理的模块,提高了开发效率和代码质量。然而,组件化也带来了一些挑战,如组件粒度的划分、组件间依赖关系的管理以及跨技术栈通信等。为了实现高质量的组件化项目,需要遵循一些实践规范和原则,如组件拆分原则、组件间依赖管理以及质量保障措施。
那么我们在进行鸿蒙开发时如何进行组件化开发呢,接下来我将带大家了解鸿蒙开发中的组件化,项目的目录结构如下
其中features目录下是组件/模块,包含不同的功能分区,entity是项目的主入口也就是hap包,commons目录下有3个har组件,分别是utils:所有的帮助类、uicomponents:项目中需要用到的自定义UI组件等、RouterModule:项目的路由(承载了整个项目跨组件通信的能力)
接下来我们重点说一下RouterModule
不同组件之间想要通信,需要建立路由联系,RouterModule模块的实现主要包含以下步骤:
- 定义路由表和路由栈
TypeScript
export class RouterModule {
// WrappedBuilder支持@Builder描述的组件以参数的形式进行封装存储
static builderMap: Map<string, WrappedBuilder<[object]>> = new Map<string, WrappedBuilder<[object]>>();
// 初始化路由栈,需要关联Navigation组件
static navPathStack: NavPathStack = new NavPathStack();
}
- 路由表增加路由注册和路由获取方法,业务har模块通过路由注册方法将需要路由的页面组件委托给RouterModule管理,增加路由跳转方法,业务har模块通过调用该方法并指定跳转信息实现模块间路由跳转,完整代码如下
TypeScript
/**
* @FileName : RouterModule
* @Author : kirk.wang
* @Time : 2024/7/10 10:44
* @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为了方便在页面跳转直接进行传值,完整代码如下:
TypeScript
/**
* @FileName : RouterModel
* @Author : kirk.wang
* @Time : 2024/9/13 14:33
* @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的页面功能。主要步骤如下:
-
在工程主入口模块Entry.hap中引入RouterModule模块和所有需要进行路由注册的业务har模块。
TypeScript// Entry.hap中的oh-package.json5文件 "dependencies": { "@ohos/home": "file:../features/home", "@ohos/report": "file:../features/report", "@ohos/shopcart": "file:../features/shopcart", "@ohos/mine": "file:../features/mine", "@ohos/utils": "file:../commons/utils", "@ohos/routermodule": "file:../commons/RouterModule" }
-
在工程主入口模块的首页Navigation组件上关联RouterModule模块的路由栈和路由表。
TypeScriptimport HomeTabs from './HomeTabs' import { BuilderNameConstants, buildRouterModel, RouterModule, RouterNameConstants } from '@ohos/routermodule'; @Preview @Entry @Component struct Index { @State homeTab: number = 0 @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) { // 从RouterModule中获取全局路由表 RouterModule.getBuilder(builderName).builder(param); } build() { // 绑定RouterModule中路由栈 Navigation(this.entryHapRouter) { Column() { HomeTabs({ currentIndex: this.homeTab }) }.backgroundColor('#f1f3f5').width('100%').height('100%') } .navDestination(this.routerMap); // 从RouterModule中获取全局路由表 } }
-
在har中声明需要跳转的页面,并且调用registerBuilder接口将页面注册到RouterModule模块的全局路由表上。以下注册逻辑会在harB的B1页面被首次加载时触发
TypeScript// harhome模块的登录页面 import { CommonConstants } from '@ohos/utils'; import router from '@ohos.router'; import { BuilderNameConstants, buildRouterModel, RouterModule, RouterNameConstants, } from '@ohos/routermodule'; @Builder export function harBuilder(value: object) { NavDestination() { Column() { ///... }.backgroundColor($r('app.color.page_background')) } .width('100%') .height('100%') } .title('登录') .onBackPressed(() => { RouterModule.pop(RouterNameConstants.ENTRY_HAP); return true; }) } const builderName = BuilderNameConstants.MINE_LOGIN; if (!RouterModule.getBuilder(builderName)) { let builder: WrappedBuilder<[object]> = wrapBuilder(harBuilder); RouterModule.registerBuilder(builderName, builder); }
-
在harHome模块中的页面调用RouterModule模块的push方法实现跳转到harMine的Login页面。当harMine的Login页面被首次通过push方法跳转时,会动态加载Login页面,并且触发步骤3中Login页面的路由注册逻辑,把Login页面注册到RouterModule的全局路由表builderMap中。
TypeScriptimport { BuilderNameConstants, buildRouterModel, RouterModule, RouterNameConstants, } from '@ohos/routermodule'; @Entry @Component export struct HomePage { build() { Column() { Button('go loagin', { stateEffect: true, type: ButtonType.Capsule }) .width('80%') .height(40) .margin(20) .onClick(() => { buildRouterModel(RouterNameConstants.ENTRY_HAP, BuilderNameConstants.MINE_LOGIN); }) }.width('100%').height('100%').backgroundColor('#F1F3F5') } }
上述方案,当在entry模块页面上点击跳转到harA模块的页面时序图如下:
具体实现
在harMine的对外导出类Index.ets中定义加载时的初始化函数harInit,该函数对harMine中需要注册路由的页面组件进行加载管理,被调用时将根据不同的路径动态加载不同的页面。as
TypeScript
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中添加所有的动态库
TypeScript
"buildOption": {
"arkOptions": {
"runtimeOnly": {
"sources": [
],
"packages": [
"@ohos/home",
"@ohos/report",
"@ohos/shopcart",
"@ohos/mine",
"@ohos/utils",
"@ohos/routermodule"
]
}
}
},
接下来在点击主页的登录按钮,就能实现从主页跳转到登录页,返回后依然能返回到前面的页面。更多功能同学们可以继续探索,官方文档地址:文档中心