1. 项目背景与核心价值
在鸿蒙应用开发中,页面路由管理一直是影响开发效率和维护成本的关键环节。HMRouter作为鸿蒙官方提供的路由框架,虽然功能完善但直接使用起来略显繁琐。这个系列教程的第三部分,我们将深入探讨如何基于HMRouter进行二次封装,打造更适合团队协作的开发工具。
我在多个鸿蒙商业项目中发现,未经封装的路由调用存在三大痛点:一是路由路径硬编码导致维护困难;二是跳转参数处理不够直观;三是缺少统一的拦截器机制。通过本教程的封装方案,可以实现路由配置集中管理、类型安全的参数传递、以及可扩展的路由拦截体系。
2. 基础封装结构设计
2.1 路由配置中心化
首先建立路由表管理类RouterConfig,使用静态常量定义所有路由路径:
class RouterConfig { // 主页模块 static readonly HOME = { MAIN: "/home/main", DETAIL: "/home/detail" }; // 用户模块 static readonly USER = { LOGIN: "/user/login", PROFILE: "/user/profile" }; }这种组织方式相比直接字符串有三大优势:
- 路径修改只需调整一处
- 智能提示避免拼写错误
- 模块化分类更清晰
2.2 路由服务层封装
创建RouterService作为路由调用的统一入口:
class RouterService { private static async routeTo( path: string, params?: object, config?: RouterOptions ) { try { await router.pushUrl({ url: path, params, ...config }); } catch (error) { console.error(`[Router Error] ${path}:`, error); } } static toHomeMain() { return this.routeTo(RouterConfig.HOME.MAIN); } static toUserProfile(userId: string) { return this.routeTo(RouterConfig.USER.PROFILE, { userId }); } }关键设计点:
- 私有方法处理底层调用
- 公共方法提供类型安全接口
- 自动错误捕获机制
3. 高级功能实现
3.1 参数类型安全方案
通过泛型约束实现参数类型检查:
interface RouteParams { [key: string]: string | number | boolean; } class RouterService { static toPage<T extends RouteParams>( path: string, params?: T, config?: RouterOptions ) { // 参数类型校验逻辑 if (params) { Object.entries(params).forEach(([key, value]) => { const type = typeof value; if (!["string", "number", "boolean"].includes(type)) { throw new Error(`Invalid param type for ${key}: ${type}`); } }); } return this.routeTo(path, params, config); } }3.2 路由拦截器体系
实现洋葱圈模型的拦截器:
type Interceptor = (context: { path: string; params?: object; next: () => Promise<void>; }) => Promise<void>; class RouterService { private static interceptors: Interceptor[] = []; static use(interceptor: Interceptor) { this.interceptors.push(interceptor); } private static async executeInterceptors( path: string, params?: object ) { const chain = this.interceptors.reduceRight( (next, interceptor) => async () => { await interceptor({ path, params, next }); }, async () => { await router.pushUrl({ url: path, params }); } ); await chain(); } }典型应用场景:
- 登录状态检查
- 页面权限验证
- 路由埋点统计
4. 性能优化实践
4.1 路由预加载机制
在应用启动时预加载常用路由:
class RouterPreloader { private static preloadedRoutes = new Set<string>(); static async preload(routes: string[]) { await Promise.all( routes.map(async (route) => { if (!this.preloadedRoutes.has(route)) { await router.preloadUrl({ url: route }); this.preloadedRoutes.add(route); } }) ); } } // 应用启动时调用 RouterPreloader.preload([ RouterConfig.HOME.MAIN, RouterConfig.USER.PROFILE ]);4.2 路由缓存策略
实现基于LRU的路由组件缓存:
const MAX_CACHE_SIZE = 5; const routeCache = new LRUCache<string, Ability>(MAX_CACHE_SIZE); class RouterService { private static async getCachedAbility( path: string ): Promise<Ability | null> { if (routeCache.has(path)) { return routeCache.get(path); } return null; } static clearCache(path?: string) { if (path) { routeCache.delete(path); } else { routeCache.clear(); } } }5. 调试与问题排查
5.1 路由日志系统
开发环境开启详细路由日志:
class RouterLogger { private static enabled = process.env.NODE_ENV === "development"; static log(path: string, params?: object) { if (this.enabled) { console.groupCollapsed(`[Router] ${path}`); console.table(params || {}); console.groupEnd(); } } } // 在RouterService中调用 RouterLogger.log(path, params);5.2 常见问题解决方案
页面不刷新问题:
- 检查路由路径是否唯一
- 确认Ability的onWindowStageCreate是否正确处理
参数传递失败:
- 确保参数值都是可序列化类型
- 接收端使用router.getParams()获取
拦截器不生效:
- 检查拦截器注册顺序
- 确认每个拦截器都调用了next()
6. 工程化实践建议
6.1 多模块路由管理
大型项目建议按功能模块拆分路由配置:
src/ modules/ home/ routes.ts user/ routes.ts product/ routes.ts router/ index.ts service.ts types.ts6.2 路由测试方案
编写路由单元测试的要点:
describe("RouterService", () => { beforeEach(() => { RouterService.clearCache(); RouterService.clearInterceptors(); }); it("should navigate to home page", async () => { const spy = jest.spyOn(router, "pushUrl"); await RouterService.toHomeMain(); expect(spy).toHaveBeenCalledWith({ url: "/home/main" }); }); });7. 进阶扩展方向
7.1 动态路由支持
实现基于服务端配置的动态路由:
class DynamicRouter { static async loadConfig() { const response = await fetch("/api/routes"); const routes = await response.json(); routes.forEach(route => { RouterConfig.addDynamicRoute( route.path, route.component ); }); } }7.2 路由动画定制
封装常用过渡动画效果:
class RouteAnimations { static getSlideIn() { return { enter: { effect: "slide", duration: 300 }, exit: { effect: "fade", duration: 200 } }; } } // 使用示例 RouterService.toDetailPage(params, { routerAnimation: RouteAnimations.getSlideIn() });在实际项目中,这套封装方案使我们的路由相关代码量减少了40%,同时类型安全性和可维护性显著提升。特别是在需要调整大量路由路径的迭代中,集中管理的优势体现得淋漓尽致。