鸿蒙技术分享:Navigation页面容器封装-鸿蒙@fw/router框架源码解析(三)

news/2024/12/2 8:39:58/

本文是系列文章,其他文章见:
鸿蒙@fw/router框架源码解析(一)-router页面管理
鸿蒙@fw/router框架源码解析(二)-Navigation页面管理
鸿蒙@fw/router框架源码解析(四)-路由Hvigor插件实现原理
鸿蒙@fw/router框架源码解析(五)-无代码依赖如何实现拦截器逻辑
鸿蒙@fw/router框架源码解析(六)-模块化开发如何实现代码解耦

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

介绍

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

具体功能介绍见https://juejin.cn/post/7386917612675301388@fw/router:鸿蒙模块化路由框架,助力开发者实现高效模块化开发!

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

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

详见gitee传送门

代码解析

FWNavigation

Navigation容器设置

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

具体如下:

  @BuilderpageMap(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)}@BuilderpageMap(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: Functionbuild() {Navigation(this.pageStack) {this.closure()}.navDestination(this.pageMap).titleMode(NavigationTitleMode.Mini).attributeModifier(this.modifier)}
}

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

FWNavigation容器设置
@Component
export struct FWNavigation {@BuilderpageMap(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: Functionbuild() {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: FWNavigationOptionsbuild() {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一开始进行封装的原因,对于鸿蒙开发而言,能够避坑的封装其实越早越好。


http://www.ppmy.cn/news/1551705.html

相关文章

后端-mybatis的一对多

分类表和菜单表是一对多的关系,菜单表对分类表是一对一的关系,我们拿前者来写一对多。 在分类表中加上一行属性list集合 最好new一下 写查询方法 写查询的sql语句 写分类表中普通字段的映射 写菜单表的字段映射,是集合。使用collection标…

Docker 清理镜像策略详解

文章目录 前言一、删除 Docker 镜像1. 查看当前镜像2. 删除单个镜像3. 删除多个镜像4. 删除所有未使用的镜像5. 删除悬空的 Docker 镜像6. 根据模式删除镜像7. 删除所有镜像 二、删除 Docker 容器1. 查找容器2. 删除一个或多个特定容器3. 退出时删除容器4. 删除所有已退出的容器…

4.8E-R图

ER图 里面的东西说的是对应关系,就是说股东持有民宿的数量从1到n,民宿对股东就是民宿对应的股东是从1到n 例子 连接数据库

SeggisV1.0 遥感影像分割软件【源代码】讲解

在此基础上进行二次开发,开发自己的软件,例如:【1】无人机及个人私有影像识别【2】离线使用【3】变化监测模型集成【4】个人私有分割模型集成等等,不管是您用来个人学习还是公司研发需求,都相当合适,包您满…

CLIP-MMA: Multi-Modal Adapter for Vision-Language Models

当前的问题 CLIP-Adapter仅单独调整图像和文本嵌入,忽略了不同模态之间的交互作用。此外,适应性参数容易过拟合训练数据,导致新任务泛化能力的损失。 动机 图1所示。多模态适配器说明。 通过一种基于注意力的 Adapter ,作者称之…

elementUI el-image的使用

elementUI组件 el-image 慎用lazy属性 <el-image :src"item.picture" alt"" lazy :title"item.title"></el-image> lazy懒加载属性会导致有些图片无法显示

一万台服务器用saltstack还是ansible?

一万台服务器用saltstack还是ansible? 选择使用 SaltStack 还是 Ansible 来管理一万台服务器&#xff0c;取决于几个关键因素&#xff0c;如性能、扩展性、易用性、配置管理需求和团队的熟悉度。以下是两者的对比分析&#xff0c;帮助你做出决策&#xff1a; SaltStack&…

位运算在嵌入式系统开发中的应用

目录 一、数据存储与节省 “绝技” 1.1. 传感器数据存储挑战 1.2. 位运算解决方案 1.2.1. 数据整合 1.2.2. 数据提取 1.3. 收益分析 二、硬件控制 “精准操纵术” 2.1. 位运算操控硬件寄存器的实例 2.2. 位运算在硬件控制中的优势 2.3. 电机驱动芯片寄存器控制示例 …