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

news2024/12/26 21:23:49

本文是系列文章,其他文章见:
鸿蒙@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页面想要正常打开,除了注册页面外,还需要对导航容器进行设置。

具体如下:

  @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一开始进行封装的原因,对于鸿蒙开发而言,能够避坑的封装其实越早越好。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2252298.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Docker中配置Mysql主从备份

Mysql配置主从备份 一、Docker中实现跨服务器主从备份二、配置步骤1.配置主库2.配置从库3.遇到问题3.其它使用到的命令 一、Docker中实现跨服务器主从备份 在 Docker 中配置 MySQL 主从备份主要通过 MySQL 主从复制实现 二、配置步骤 1.配置主库 # 进入mysql主库容器 docke…

组态王实现设备OEE数据采集的项目总结

组态王实现设备OEE数据采集的项目总结 方案设计硬件设计软件设计 难点总结数据通讯数据转换报表数据存储 项目拓展 最近在做一个项目,关于采集设备OEE数据。陆陆续续做了小半个月,现在算告一段落,特整理总结。 方案设计 硬件设计 本项目是…

C++《set与map》

在之前我们已经学习了解了CSTL当中的string和vector等容器,现在我们已经懂得了这些容器提供的接口该如何使用,并且了解了这些容器的底层结构。接下来我们在本篇当中将继续学习STL内的容器set与map,在此这两个容器与我们之前学习的容器提供的成…

FPGA 15 ,Xilinx Vivado 的基本使用,使用 Vivado 点亮LED灯,具体实现(使用赛灵思 Vivado编写 Verilog 代码)

目录 前言 我们都知道 Xilinx Vivado 是一款强大的 FPGA 集成开发环境,支持从设计输入、综合、实现到仿真的全流程。它兼容 Verilog 和 VHDL 等硬件描述语言,具备高层次综合功能,能有效提升复杂数字系统的设计效率和性能优化。这里来分享记…

VxLAN 集中式网关配置实验

一、拓扑: 二、配置思路: 1、CE1、2、3 配置 IGP,如 RIP 2、CE1、3 配置 BD 域并绑定二层 VNI,起用 NVE 并建立 VxLAN 隧道:源是自己的环回口,对端是 CE2、3 的环回口,用二层 VNI 联接&#x…

qml项目创建的区别

在Qt框架中,你可以使用不同的模板来创建应用程序。你提到的这几个项目类型主要针对的是Qt的不同模块和用户界面技术。下面我将分别解释这些项目类型的区别: 根据你提供的信息,以下是每个项目模板的详细描述和适用场景: Qt Widgets…

仿真s7 snap7 写入

# 准备写入数据 data bytearray(20) # 创建 20 字节的缓冲区 (每个整数占 2 字节,共 10 个整数)# 按顺序将值 1, 2, 3, ..., 10 写入数据缓冲区 for i in range(10):set_int(data, i * 2, i 1) # 每个整数占 2 字节,从 i * 2 的位置开始写入整数# 将…

【计算机网络】实验6:IPV4地址的构造超网及IP数据报

实验 6:IPV4地址的构造超网及IP数据报 一、 实验目的 加深对IPV4地址的构造超网(无分类编制)的了解。 加深对IP数据包的发送和转发流程的了解。 二、 实验环境 • Cisco Packet Tracer 模拟器 三、 实验内容 1、了解IPV4地址的构造超网…

[CSP-J 2024] 小木棍

题目传送门 P11229 [CSP-J 2024] 小木棍 题解思路: 1、首先想到的是搜索,全排列填盒子的思想,小木棍数够组成某个数,把某个数放到盒子里,这里就是累加到sum上,还做了个剪枝,中间如果已经大于…

AI运用落地思考:如何用AI进行物料条码的识别及异常检测?

一、AI进行物料条码识别 (一)基于深度学习的方法 图像预处理 首先需要对包含物料条码的图像进行预处理。这包括调整图像的大小、对比度、亮度等操作,以便提高条码图像的清晰度和可识别性。例如,如果图像较暗,可以通过…

基于STM32的电能监控系统设计:ModBus协议、RS-485存储和分析电能数据(代码示例)

一、项目概述 随着智能电网的发展,电能管理的科学性与有效性变得越来越重要。本项目旨在设计并实现一个基于STM32103C8T6单片机的电能监控系统,该系统可以实时采集、存储和分析电能数据,帮助用户实现对电能的高效管理。 项目目标 实时监控&…

从数据库模型设计到字段设计,用自然语言实现数据库开发,颠覆传统的数据库开发模式

前言 在数据库开发过程中,开发者经常面临以下困扰: 1. 焦头烂额的数据库设计阶段 在设计数据库阶段,开发者需要全面考虑表结构、关系模型、字段定义等,稍有不慎就会影响后续的开发与维护,常常让人感到无从下手。 2…

51c自动驾驶~合集39

我自己的原文哦~ https://blog.51cto.com/whaosoft/12707676 #DiffusionDrive 大幅超越所有SOTA!地平线DiffusionDrive:生成式方案或将重塑端到端格局? 近年来,由于感知模型的性能持续进步,端到端自动驾驶受到了来…

docker安装hadoop环境

一、使用docker搭建基础镜像 1、拉取centos系统镜像 # 我这里使用centos7为例子 docker pull centos:7 2、创建一个dockerfiler文件,用来构建自定义一个有ssh功能的centos镜像 # 基础镜像 FROM centos:7 # 作者 #MAINTAINER hadoop ADD Centos-7.repo /etc/yum.re…

多级缓存设计实践

缓存是什么? 缓存技术是一种用于加速数据访问的优化策略。它通过将频繁访问的数据存储在高速存储介质(如内存)中,减少对慢速存储设备(如硬盘或远程服务器)的访问次数,从而提升系统的响应速度和…

状态模式S

状态模式(State Pattern)是行为设计模式的一种,它允许一个对象在其内部状态发生改变时改变其行为。这个对象被视为类型的有限状态机(Finite State Machine)。 在状态模式中,我们创建表示各种状态的对象和一…

数据结构 (23)并查集与等价类划分

一、并查集 并查集(Union-Find Set或Disjoint Set)是一种数据结构,用于处理一些不相交集合(disjoint sets)的合并及查询问题。它通常表示为森林,并用数组来实现(类似于二叉堆)。在并…

【Linux】开启你的Linux之旅:初学者指令指南

Linux相关知识点可以通过点击以下链接进行学习一起加油! 在 Linux 开发中,GDB 调试器和 Git 版本控制工具是开发者必备的利器。GDB 帮助快速定位代码问题,Git 则提供高效的版本管理与协作支持。本指南将简明介绍两者的核心功能与使用技巧&…

Python语法1

Python语法1 作者:王珂 邮箱:49186456qq.com 文章目录 Python语法1[TOC] 前言一、环境搭建1.1 安装Python解释器1.2 安装第三方包1.3 安装Pycharm1.4 虚拟环境 二、Python语法2.1 基础语法2.1.1 注释2.1.2 变量2.1.3 数据类型2.1.4 关键字和标识符 2.2…

普及组集训--图论最短路径

定义:表示顶点u到顶点v的一条边的权值(边权) 最短路径算法有常见的四种:floyd,dijkstra,Bellman-Ford,SPFA 不过Bellman-Ford并不常用,所以本文不提; 重点在于dijkstr…