鸿蒙技能分享:鸿蒙应用开辟从入门到入魔:Navigation路由管理为什么这么麻 ...

打印 上一主题 下一主题

主题 1030|帖子 1030|积分 3090

鸿蒙应用开辟从入门到入魔:Navigation路由管理为什么这么贫苦?

鸿蒙应用的路由管理

鸿蒙应用开辟,ArkUI框架提供了两种路由管理方式,分别是@ohos.router(以下简称router)和Navigation。  
router管理页面

我们先来看router是怎么管理页面的。
router的页面通过@Entry来声明,支持平常路由和命名路由。  
  1. // 定义页面
  2. @Entry
  3. @Component
  4. struct SecondPage {
  5.   build() {
  6.     Column() {
  7.       Text('这是SecondPage')
  8.     }
  9.     .height('100%')
  10.     .width('100%')
  11.   }
  12. }
  13. // 打开
  14. router.pushUrl({ url: 'pages/SecondPage' })
  15. // 定义命名路由页面
  16. @Entry({ routeName: 'libraryHar/mainPage' })
  17. @Component
  18. export struct MainPage {
  19.   @State message: string = 'Hello World';
  20.   build() {
  21.     Row() {
  22.       Column() {
  23.         Text(this.message)
  24.           .fontSize(50)
  25.           .fontWeight(FontWeight.Bold)
  26.       }
  27.       .width('100%')
  28.     }
  29.     .height('100%')
  30.   }
  31. }
  32. // 打开
  33. import 'libraryhar/src/main/ets/components/MainPage';
  34. router.pushNamedRoute({ name: 'libraryHar/mainPage' })
复制代码
固然router的平常路由和命名路由的区分让项目开辟产生了一些额外的复杂度,但router的页面定义方式还是比力符合常用风俗的。
Navigation管理页面

再让我们来看看Navigation是怎么管理页面的。
  1. @Entry
  2. @Component
  3. struct NavigationIndex {
  4.   navPathStack: NavPathStack = new NavPathStack()
  5.   @Builder
  6.   routerMap(name: string, params: ESObject) {
  7.     if ('SecondNavDestination' === name) {
  8.       SecondNavDestination()
  9.     } else {
  10.       Text('404 Not Found')
  11.     }
  12.   }
  13.   build() {
  14.     Navigation(this.navPathStack) {
  15.       Column() {
  16.         Text('打开SecondNavDestination')
  17.           .onClick(() => {
  18.             this.navPathStack.pushDestination({ name: 'SecondNavDestination' })
  19.           })
  20.       }
  21.     }
  22.     .navDestination(this.routerMap)
  23.   }
  24. }
复制代码
Navigation是导航容器,具体的页面实在是NavDestination。
Navigation提供了navDestination方法用来动态创建NavDestination页面。
固然Navigation的页面管理提供了动态化的能力,但现在的这种写法无法做到解耦,每新增一个页面都要增长一个if语句。
这种方式和router的管理方式相比差别很大,无法满足开辟者对于解耦大概动态化的要求。
怎样动态化

通过上一节的例子我们可以看到,Navigation标准的路由管理是用builder方法来实现的,假如想要更好的利用Navigation路由,我们首先想到的就是把NavDestination的实例化过程拆分出去,不直接在builder方法中进行。
装饰器?

router是通过@Entry装饰器来声明页面和路由的,Navigation并没有提供类似的装饰器。  
那么我们是否可以提供一个类似的装饰器完成相干功能呢?
好比:
  1. interface NavigationEntryOptions {
  2.   routeName: string
  3. }
  4. function NavigationEntry(options: NavigationEntryOptions) {
  5.   return Object
  6. }
  7. @NavigationEntry({ routeName: "testPage" });
  8. export struct TestPage {
  9.   build() {
  10.    
  11.   }
  12. }
复制代码
但是实际上不行,在ArkTS中,struct不支持添加自定义装饰器,固然编译器不会报错,但是装饰器却不执行。
反射创建实例?

装饰器不可以,我们然后想到的方法天然是反射,既然要用routeName获取一个Component对象。
但是可惜,ArkTS不允许利用TS的动态化方法:
  1. 限制使用标准库
  2. 规则:arkts-limited-stdlib
  3. 级别:错误
  4. ArkTS不允许使用TypeScript或JavaScript标准库中的某些接口。大部分接口与动态特性有关。
复制代码
从TS到ArkTS的适配规则
动态返回实例

既然不让反射创建实例,那我们直接创建好实例,然后和routeName设置好匹配关系,然后动态返回呢?
  1. struct WrappedNavigationIndex {
  2.   navDestinationMap: Record<string, ESObject> = {}
  3.   aboutToAppear(): void {
  4.     this.navDestinationMap['WrappedSecondNavDestination'] = WrappedSecondNavDestination()
  5.   }
  6.   @Builder
  7.   routerMap(name: string, params: ESObject) {
  8.     this.navDestinationMap[name]
  9.   }
  10. }
复制代码
还是不行,Navigation.navDestination方法对于返回值范例有要求,必须是@Component装饰的实例。  
  1. 'PageManager.shared' does not comply with the UI component syntax. <ArkTSCheck>
复制代码
封装

因为ArkUI的页面是基于struct而不是class,所以无法继承,也无法声明一个符合comply with the UI component syntax要求的范例。
看似陷入了死胡同,不外官方也考虑到了,所以提供了一个特殊范例:WrappedBuilder。
可以利用wrapBuilder函数创建该范例,但需要传入一个@builder装饰的函数作为参数。
  1. @Entry
  2. @Component
  3. struct WrappedNavigationIndex {
  4.   navDestinationMap: Record<string, WrappedBuilder<[params?: ESObject]>> = {}
  5.   aboutToAppear(): void {
  6.     this.navDestinationMap['WrappedSecondNavDestination'] = wrapBuilder(WrappedSecondNavDestinationBuilder)
  7.   }
  8.   @Builder
  9.   routerMap(name: string, params: ESObject) {
  10.     this.navDestinationMap[name].builder()
  11.   }
  12. }
  13. @Builder
  14. export function  WrappedSecondNavDestinationBuilder() {
  15.   WrappedSecondNavDestination()
  16. }
  17. @Component
  18. export struct WrappedSecondNavDestination {
  19.   build() {
  20.     NavDestination() {
  21.       Column() {
  22.         Text('这是WrappedSecondNavDestination')
  23.       }
  24.     }
  25.   }
  26. }
复制代码
怎样自动化注册?

通过封装builder,我们做到了动态化返回NavDestination,但NavDestination实例我们现在需要手动一个个创建并注册。
那么有没有办法不需要手动注册,而是像@Entry那样自动创建并注册吗?  
注册表:官方方案

我们先来看看官方提供的方案。
方案详情请查看官方文档:系统路由表
  1.   {
  2.     "routerMap": [
  3.       {
  4.         "name": "PageOne",
  5.         "pageSourceFile": "src/main/ets/pages/PageOne.ets",
  6.         "buildFunction": "PageOneBuilder",
  7.         "data": {
  8.           "description" : "this is PageOne"
  9.         }
  10.       }
  11.     ]
  12.   }
复制代码
我们可以看到在路由表配置文件中,有一个字段是buildFunction,字段阐明为跳转目标页的入口函数名称,必须以@Builder修饰。。
这即是我们文章上面定义的页面builder方法。
系统路由表通过自动剖析路由表配置,将页面name和buildFunction包装成的WrappedBuilder实例关联在一起,从而做到自动化注册效果。
代码生成+注册表:鸿蒙案例库方案

该方案是指cases开源案例库中的dynamicRouter。
其原理见自动生成动态路由。
最为焦点的功能在于添加装饰器和插件配置文件,编译时自动生成动态路由表,其通过自定义装饰器和hvigor插件,自动生成builder方法和路由表。
其中的插件就是指cases库中的AutoBuildRouter。
我们来看一下dynamicRouter生成的代码和路由表是什么样的。
  1. // auto-generated
  2. import { DynamicsRouter, AppRouterInfo } from '@ohos/dynamicsrouter/Index';
  3. import { AddressExchangeView } from '../view/AddressExchangeView'
  4. @Builder
  5. function addressExchangeViewBuilder() {
  6.   AddressExchangeView();
  7. }
  8. export function addressExchangeViewRegister(routerInfo: AppRouterInfo) {
  9.   DynamicsRouter.registerAppRouterPage(routerInfo, wrapBuilder(addressExchangeViewBuilder));
  10. }
复制代码
  1. {
  2.   "routerMap": [
  3.     {
  4.       "name": "addressexchange/AddressExchangeView",
  5.       "pageModule": "addressexchange",
  6.       "pageSourceFile": "src/main/ets/generated/RouterBuilder.ets",
  7.       "registerFunction": "addressExchangeViewRegister"
  8.     }
  9.   ]
  10. }
复制代码
这个路由表和系统路由表的最大差别就是没有了buildFunction字段,而多了registerFunction字段。
它是做什么用的呢?
  1.       import(`${DynamicsRouter.config.libPrefix}/${routerInfo.pageModule}`)
  2.         .then((module: ESObject) => {
  3.           // 通过路由注册方法注册路由
  4.           module[routerInfo.registerFunction!](routerInfo);
  5.           // TODO:知识点:在路由模块的动态路由.pushUri()中调用拦截方法,此处必须等待动态路由加载完成后再进行拦截,否则页面加载不成功,导致无法注册拦截的函数,出现首次拦截失效。
  6.           if (Interceptor.interceptor(name, param)) {
  7.             return;
  8.           }
  9.           // 跳转页面
  10.           DynamicsRouter.navPathStack.pushPath({ name: name, param: param });
  11.         })
  12.         .catch((error: BusinessError) => {
  13.           logger.error(`promise import module failed, error code:${error.code}, message:${error.message}`);
  14.         });
复制代码
在模块按需加载成功后,会调用module对象中的registerFunction方法完成页面路由注册。
可以看到,案例库方案相对于系统方案少了编写builder方法和配置路由表的步骤,将这些操纵交给hvigor插件去完成。
代码生成+装饰器:@fw/router方案

无论是官方方案和案例库方案,都要依赖路由表。那么到底有没有办法不依赖路由表呢?
有的。
我们上面说到,struct可以添加自定义装饰器,但自定义装饰器不会触发执行;但是class的装饰器却是可以触发执行的。
所以,可以通过class装饰器完成路由的自动注册。而这就是https://gitee.com/FantasyWind/fwrouter@fw/router的实现方案。
因为struct装饰器不能自动执行,所以@fw/router也是借用hvigor插件扫描struct装饰器并生成模板代码。  
我们来看一下它生成的页面路由代码。
  1. // auto-generated
  2. import { RouterClassProvider } from '@fw/router/Index';
  3. import { TestDestination } from '../pages/TestPage'
  4. @Builder
  5. function testDestinationBuilder(params: ESObject) {
  6.   TestDestination({ params: params });
  7. }
  8. @RouterClassProvider({ routeName: 'testPage', builder: wrapBuilder(testDestinationBuilder) })
  9. export class TestDestinationProvider {
  10. }
复制代码
我们又看到了认识的builder方法,不外和案例库方案差别的是,这里多了TestDestinationProvider类,而它有个装饰器@RouterClassProvider。
@RouterClassProvider实现了什么功能呢?
  1. export function RouterClassProvider(options: RouterClassProviderOptions) {
  2.   return (target: ESObject) => {
  3.     RouterManagerForNavigation.getInstance().registerBuilder(options.routeName, options.builder)
  4.   }
  5. }
复制代码
该装饰器在执行时完成了页面路由的注册。更巧的是这个装饰器在模块被动态import时就会触发执行,因此我们既不需要路由表,也不需要在import之后手动调用注册函数。
不外该方案也存在一些缺点,好比现在(api12),类装饰器在hsp包中无法触发执行,因此只能在hap和har中利用。
总结

看完整篇文章我们看到,Navigation路由管理方案之所以酿成如今的样子,实在是开辟语言存在的诸多限定所导致。

  • struct不支持自定义装饰器;
  • Navigation跳转不支持直接动态实例化@Component;(因为@Component struct没有范例,只能是ESObject,Navigation.navDesination不支持ESObject)所以只能是用wrapBuilder包装@Builder;
  • 不支持反射,动态实例化,@Builder不能动态创建组件,一个Component必须对应一个@Builder;
哪怕官方能小小地放开一个口子,Navigation页面的管理都不会这么复杂[捂脸]。
只能盼望官方能尽快优化一下利用方式吧。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

反转基因福娃

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表