【鸿蒙实战开发】鸿蒙@fw/router框架源码解析(三)

55 篇文章 0 订阅
55 篇文章 0 订阅

鸿蒙@fw/router框架源码解析

介绍

@fw/router是在HarmonyOS鸿蒙系统中开发应用所使用的开源模块化路由框架。
该路由框架基于模块化开发思想设计,支持页面路由和服务路由,支持自定义装饰器自动注册,与系统路由相比使用更便捷,功能更丰富。

基于模块化的开发需求,本框架支持以下功能:

  • 支持页面路由和服务路由;
  • 页面路由支持多种模式(router模式,Navigation模式,混合模式);
  • router模式支持打开非命名路由页面;
  • 页面打开支持多种方式(push/replace),参数传递;关闭页面,返回指定页面,获取返回值,跨页面获取返回值;
  • 支持服务路由,可使用路由url调用公共方法,达到跨技术栈调用以及代码解耦的目的;
  • 支持页面路由/服务路由通过装饰器自动注册;
  • 支持动态导入(在打开路由时才import对应har包),支持自定义动态导入逻辑;
  • 支持添加拦截器(打开路由,关闭路由,获取返回值);
  • Navigation模式下支持自定义Dialog对话框;

代码解析

FWNavigation

Navigation容器设置

因为本文章侧重于讲解@fw/router的实现逻辑,所以在上一节中并没有完整的讲Navigation页面如何使用。
其实,Navigation页面想要正常打开,除了注册页面外,还需要对导航容器进行设置。

具体如下:


 @Builder
 pageMap(name: string, param?: ESObject) {
 if (RouterManagerForNavigation.getInstance().getBuilder(name).builder.length == 0) {
 RouterManagerForNavigation.getInstance().getBuilder(name).builder()
 } else {
 RouterManagerForNavigation.getInstance().getBuilder(name).builder(param)
 }
 }

 build() {
 Navigation(this.pageStack) {
 // ...
 }
 .navDestination(this.pageMap)
 }

核心就是两点:

  1. Navigation容器需要绑定NavPathStack对象;
  2. Navigation容器需要设置navDestination方法,因为它才是真正的页面跳转处理逻辑;

如果你的应用只使用Navigation进行页面管理,那么可能就只有一个Navigation容器,上面这些代码只需要设置一次,手动编写没什么问题。
但如果你准备router页面栈和Navigation页面栈混用,或者主用router页面栈但Dialog想用Navigation支持,那么理论上每个router页面都需要一个Navigation容器,上面的设置代码就需要写多次。
正是基于以上原因,@fw/router中封装了FWNavigation容器。

FWNavigation整体代码
@Component
export struct FWNavigation {
 // 接受外部传入的AttributeModifier类实例
 @Prop modifier: NavigationModifier | null = null;

 @Provide('pageStack') pageStack: NavPathStack = new NavPathStack()

 aboutToAppear(): void {
 RouterManagerForNavigation.getInstance().pushNavPathStack(this.pageStack)
 }

 aboutToDisappear(): void {
 RouterManagerForNavigation.getInstance().popNavPathStack(this.pageStack)
 }

 @Builder
 pageMap(name: string, param?: ESObject) {
 if (RouterManagerForNavigation.getInstance().getBuilder(name).builder.length == 0) {
 RouterManagerForNavigation.getInstance().getBuilder(name).builder()
 } else {
 RouterManagerForNavigation.getInstance().getBuilder(name).builder(param)
 }
 }

 @BuilderParam closure: Function

 build() {
 Navigation(this.pageStack) {
 this.closure()
 }
 .navDestination(this.pageMap)
 .titleMode(NavigationTitleMode.Mini)
 .attributeModifier(this.modifier)
 }
}

FWNavigation的代码不多,但是大致也分为三部分,分别是容器设置,多容器逻辑,系统组件扩展。

FWNavigation容器设置

@Component
export struct FWNavigation {
 @Builder
 pageMap(name: string, param?: ESObject) {
 if (RouterManagerForNavigation.getInstance().getBuilder(name).builder.length == 0) {
 RouterManagerForNavigation.getInstance().getBuilder(name).builder()
 } else {
 RouterManagerForNavigation.getInstance().getBuilder(name).builder(param)
 }
 }

 build() {
 Navigation(this.pageStack) {
 // ...
 }
 .navDestination(this.pageMap)
 }
}

容器设置代码就是我们在上一节中讲的,主要是绑定NavPathStack对象和设置navDestination方法。

多容器逻辑

@Component
export struct FWNavigation {
 @Provide('pageStack') pageStack: NavPathStack = new NavPathStack()

 aboutToAppear(): void {
 RouterManagerForNavigation.getInstance().pushNavPathStack(this.pageStack)
 }

 aboutToDisappear(): void {
 RouterManagerForNavigation.getInstance().popNavPathStack(this.pageStack)
 }

 build() {
 Navigation(this.pageStack) {
 }
 }
}

多容器逻辑主要是解决Navigation绑定的NavPathStack对象从哪里来的问题。
如果整个应用只有一个Navigation容器,其实很简单,只需要让RouterManager单例自己创建NavPathStack对象,Navigation使用即可。
但对于应用中存在多个Navigation容器的情况,就比较复杂了。
从以上的代码中,我们看到,NavPathStack对象是由FWNavigation容器自己创建,并在aboutToAppear方法中,讲之托管给了RouterManagerForNavigation单例。
aboutToDisappear方法中,也会将我们使用的NavPathStack对象从RouterManagerForNavigation单例中移除。

export class RouterManagerForNavigation implements RouterHandler {
 // 多Navigation状态下,每个Navigation都要将自己的`navPathStacks`对象托管给管理器。
 navPathStacks: NavPathStack[] = [];

 get currentNavPathStack(): NavPathStack | undefined {
 return this.navPathStacks[this.navPathStacks.length-1]
 }

 pushNavPathStack(stack: NavPathStack) {
 this.navPathStacks.push(stack)
 }

 popNavPathStack(stack?: NavPathStack) {
 if (stack != undefined && this.navPathStacks.indexOf(stack) >= 0) {
 this.navPathStacks = this.navPathStacks.filter((item) => item !== stack)
 } else {
 this.navPathStacks.pop()
 }
 }
}

RouterManagerForNavigation主要是使用currentNavPathStack方法,所以上面的处理主要是为了让使用的NavPathStack对象和当前UI层展示的保持一致。

除了普通的push/pop场景,其实还有更复杂的情况,比如Tab嵌套。
当多个页面嵌入到Tab中时,我们建议Tab页外面统一套一层FWNavigation容器,Tab内页不套FWNavigation容器,否则当Tab页面selectedIndex变动时,还需要保证currentNavPathStack获取到的对象和当前页面的NavPathStack对象一致,否则Navigation页面无法正常显示。

系统组件扩展

FWNavigation容器其实也只是对系统Navigation容器进行了封装,为了更好的兼容性,理论上我们需要支持所有Navigation支持的属性。
好消息是,官方给我们提供了方案:AttributeModifier。

@Component
export struct FWNavigation {
 // 接受外部传入的AttributeModifier类实例
 @Prop modifier: NavigationModifier | null = null;

 @BuilderParam closure: Function

 build() {
 Navigation(this.pageStack) {
 this.closure()
 }
 .attributeModifier(this.modifier)
 }
}

使用起来也算方便:

export struct TestPage {
 @State modifier: NavigationModifier = new NavigationModifier()
 .mode(NavigationMode.Stack)
 .subTitle('TestPage')

 build() {
 Column() {
 FWNavigation({ modifier: this.modifier }) {
 TestPageContent({ pageName: 'TestPage' })
 }
 }
 }
}

但坏消息是,即便是系统自己实现的NavigationModifier,也并不是所有方法都可以使用。
有些属性你在IDE里可以调用,但运行会报错。

Error message:Method not implemented.

当你遇到这个报错时,很不幸,你要使用的属性并不支持。

其他方案

那么,除了AttibuteModifier,还有其他方案吗?

肯定有,比如可以将Navigation所有支持的参数放到FWNavigation的构造方法入参中,自己对接实现。

但是该方案存在几个缺点:

  1. 代码逻辑不灵活,当系统api变动时自己也需要��动;
  2. 自定义组件不能使用链式语法,自定义参数只能放在构造方法入参中;也就是说如果现有代码从Navigation写法迁移到FWNavigation,无法通过改类名的方式直接迁移;(当然AttributeModifier也不行)
  3. 还有就是自己实现,无法使用系统api的默认取值。

第三个问题或许有点难以理解,下面详细解释下。

比如,Navigation有个属性叫hideToolBar是否隐藏工具栏。默认值:false。true: 隐藏工具栏。false: 显示工具栏。

我们看到系统的默认值现在是false。

我们在封装时,代码类似于:

class FWNavigationOptions {
 hideToolBar?: boolean
}

struct FWNavigation {
 @Prop options: FWNavigationOptions

 build() {
 Navigation() {

 }
 .hideToolBar(this.options.hideToolBar)
 }
}

现在的问题在于这一句.hideToolBar(this.options.hideToolBar)
我们自己封装的hideToolBar是可选参数,可以为undefined。
但是系统的Navigation.hideToolBar()入参却是必传参数,不能为undefined。

理论上有几种处理方法:

  1. 自己手动写死默认值:.hideToolBar(this.options.hideToolBar ?? false);但面对Navigation20多种属性,写起来也太麻烦,而且那些方法类型的属性,还需要自己实现默认的方法,太复杂;
  2. 通过条件渲染来避免.hideToolBar()方法调用;这种方法对于一两个属性的情况还行,属性多了就不行,毕竟它就是枚举,有10个属性你就要写2^10个条件分支语句;

所以最终,还是老老实实选择了AttributeModifier方案,虽然暂时还不完美,但还可以期待官方早点优化好……

总结

FWNavigation核心还是Navigation容器的封装扩展,对于@fw/router而言只是一个附加功能。

在混合栈的使用场景下,FWNavigation的价值比较明显,这也是@fw/router一开始进行封装的原因,对于鸿蒙开发而言,能够避坑的封装其实越早越好。

写在最后

●如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
●点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
●关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
●更多鸿蒙最新技术知识点,请移步前往小编:https://gitee.com/

在这里插入图片描述

  • 17
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值