路由的选择
HarmonyOS提供两种路由实现的方式,分别是 Router 和 NavPatchStack。两者使用场景和殊效各有优劣。
组件适用场景特点备注Router模块间与模块内页面切换通过每个页面的url实现模块间解耦NavPathStack模块内页面切换通过组件级路由统一起由管理
如果是单包应用开发,不使用动态包(hsp)举行拆包,只是使用静态包(har)简单的举行模块拆分,那么我保举使用 navPatchStack。
如果像开发 鸿蒙元服务,对单包体积有 2M 的限制,那么我们不得不使用动态包的方式。将相对独立的功能,二级页面等拆分出去,封装成动态包,可避开 dependencies 直接依赖得引用情势。
此时使用 router 跳转 url 的方式才可跳转到动态包内非直接引用的页面
NavPatchStatck 如何跳转(传参)及页面回调
NavPathStack 是配合 Navigation 一起使用的,
Navigation导航组件做统一的页面跳转管理,它提供了一系列属性方法来设置页面的标题栏、工具栏以及菜单栏的各种展示样式。
如何跳转(传参)及实现页面回调?
登录后复制 - //第一步:定义一个用于传参和实现页面回调的模型
- export interface RouterModel {
- params?: Object, // 要传递给跳转页面的参数
- popCallback?: (value: Object | undefined) => void // 用于页面回调
- }
复制代码
登录后复制 - //第二步,需要在应用的根页面自行维护 navStack 实例,并传递给根节点 Navigation
- @Provide('navPathStack') navPathStack: NavPathStack = new NavPathStack()
- Navigation(this.pageInfos) {
- Column() {}
- }
- .title('NavIndex')
- .navDestination(this.PageMap)
- // 统一管理维护路由跳转
- @Builder
- PageMap(name: string, params: RouterModel) {
- if (name === 'pageOne') {
- TestNavPathPage({ // TestNavPathPage 就是要跳转的目标页面
- routerParams: params
- })
- } else {
- // 默认空页面
- }
- }
复制代码
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
登录后复制 - /// 任意一个页面获取 navPathStack 调用跳转并传参
- @Component
- export struct RouterCallbackExample {
- @Consume('navPathStack') navPathStack: NavPathStack;
-
- // NavPatchStack 方式跳转并获取回调
- navPathStackJump() {
- const routerParams: RouterModel = {
- params: '我是入参 123', //传递到跳转页面的入参
- popCallback: (callbackValue) => {
- // 这里拿到回调结果,注意要判断 callbackValue !== undefine
- // 这里拿到下面目标页面回传的结果 ‘我是回调的结果 345’
- }
- }
- this.navPathStack.pushPathByName('pageOne', routerParams) // 'pageOne' 对应上面 'PageMap' 方法内定义的路径名称常量
- }
- build() {
- Button('跳转').onClick(() => {
- this.navPathStackJump()
- })
- }
- }
复制代码
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
登录后复制 - /// 目标页面接收入参、并返回页面回调
- @Component
- export struct TestNavPathPage {
- @Consume('navPathStack') navPathStack: NavPathStack;
- routerParams?: RouterModel
- @State receiveParams: string = ''
- aboutToAppear(): void {
- // 接收入参,这里拿到上面传入的 ‘我是入参 123’
- let receiveParams = this.routerParams!.params
- }
- build() {
- NavDestination() {
- Button('关闭页面并回调结果').onClick(() => {
- if (this.routerParams?.popCallback !== undefined) {
- this.routerParams.popCallback('我是回调的结果 345 ')
- }
- this.navPathStack.pop()
- })
- }.title('跳转目标页')
- }
- }
复制代码
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
Router 如何跳转(传参)及页面回调
Router 跳转可支持跳转本包内页面以及动态包大概拆包内的页面,
登录后复制 - 1. 本地包内,或者直接依赖的静态包内页面,url 定义为 : pages/Page1
- 2. 分包内的页面,url 定义为 :@bundle:com.rex.harmony.atomic.service/featureName/ets/pages/Page2
- // com.rex.harmony.atomic.service 是我的应用包名
- // featureName 是跳转页面所在的模块名称,对应 module.json5 里面额 name
- // ets/pages/Page2 为目标页面在模块内的页面路径,对应 main_pages.json 内的页面路径
复制代码
登录后复制 - router.pushUrl({ url: '', params: Object })
复制代码
- 留意: 使用 router + url 举行跳转的目的页面必须使用 @Entry 修饰,且在main_pages.json 文件内填写对应路径
重点:截止 API11 版本,router 支持传递的 params 传参,不是引用传递,以是在动态包内实际获取到的不是同一个对象,为了实现页面回调,router 我们需要做如下封装:
- 在公共的har包内定义 Router 管理类 FastRouter
(在下文扩展中解释单例为什么这么实现)
登录后复制 - import { RouterModel } from './model/RouterModel'
- import { router } from '@kit.ArkUI'
- /// 基于 router 库封装,为了实现页面回调
- export class FastRouter {
- public readonly routerStack: RouterModel[] = []
- /// 跨 hsp 使用这种方式实现单例
- public static instance(): FastRouter {
- const storageKey = 'REX_FAST_ROUTER'
- if (!AppStorage.has(storageKey)) {
- AppStorage.setOrCreate(storageKey, new FastRouter())
- }
- return AppStorage.get<FastRouter>(storageKey)!
- }
- /// 获取路由传递的入参
- public static get getRouterCurrentParams(): RouterModel | undefined {
- const stack = FastRouter.instance().routerStack
- if (stack.length === 0) {
- return undefined
- }
- return stack[stack.length - 1]
- }
- /// push 页面
- public static async push(route: RouterModel): Promise<void> {
- try {
- await router.pushUrl({ url: route.url, params: route.params })
- FastRouter.instance().routerStack.push(route)
- } catch (_) {
- console.log('>>>>')
- }
- }
- /// replace 页面
- public static async replace(route: RouterModel): Promise<void> {
- try {
- await router.replaceUrl({ url: route.url, params: route.params })
- const instance = FastRouter.instance()
- const list = instance.routerStack
- if (list.length > 0) {
- instance.routerStack.splice(instance.routerStack.length - 1, 1, route)
- }
- } catch (_) {
- // 暂无处理
- }
- }
- /// 退出栈顶页面
- public static async pop(animated?: boolean): Promise<void> {
- router.back()
- const routerStack = FastRouter.instance().routerStack
- routerStack.pop()
- }
- }
复制代码
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 任一页面使用 FastRouter 举行 url 跳转
登录后复制 - // 跳转到 hsp 包(feature_hsp_page)内的 TestHspHomePage 页面
- const routerParams: RouterModel = {
- url: '@bundle:com.rex.harmony.atomic.service/feature_hsp_page/ets/pages/TestHspHomePage',
- params: '我是入参 1488',
- popCallback: (callbackValue) => {
- if (callbackValue !== undefined) {
- //这里获取跳转页的回调数据
- //接收到下文中目标页面的回调结果:‘我是回调的结果 6100 ’
- }
- }
- }
- FastRouter.push(routerParams)
复制代码
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
登录后复制 - @Entry
- @Component
- struct Index {
- routerParams?: RouterModel
- aboutToAppear(): void {
- this.routerParams = FastRouter.getRouterCurrentParams as RouterModel
- let receiveParams = this.routerParams.params //这里接收入参,也就是上面传递的 ‘我是入参 1488’
- }
- build() {
- Button('关闭页面并回调结果').onClick(() => {
- if (this.routerParams?.popCallback !== undefined) {
- this.routerParams.popCallback('我是回调的结果 6100 ')
- }
- FastRouter.pop()
- })
- }
- }
复制代码
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
总结
NavPatchStack 和 Router 两种路由方式各有优劣,NavPatchStack 方便统一管理,Router 方便解耦,两者没有任何关联,可以一起使用,也可以单独使用。
扩展:动态包、静态包的使用差别
说到动态包(HAR)和静态包(HSP),这里扩展一下两者的区别。
静态包的 module.json5 文件,type 标识为 har
登录后复制 - {
- "module": {
- "name": "静态包模块名称",
- "type": "har",
- "deviceTypes": [
- "default",
- "tablet"
- ]
- }
- }
复制代码
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
静态包的 module.json5 文件,type 标识为 shared
登录后复制 - {
- "module": {
- "name": "动态包模块名称",
- "type": "shared",
- "description": "$string:shared_desc",
- "deviceTypes": [
- "phone",
- "tablet"
- ],
- "deliveryWithInstall": true,
- "installationFree": true,
- "pages": "$profile:main_pages"
- }
- }
复制代码
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
动态包和静态包都可以被直接引用,在 oh-package.json5 内
登录后复制 - {
- ...省略
- "dependencies": {
- "@rex/任意名称": "file:../../base/静态包模块名称",
- "@rex/任意名称": "file:../../base/动态包模块名称"
- }
- }
复制代码
重点:
- 动态包可以不直接依赖( 比方:使用 preload ),跳转动态包内的页面可以通过 router 拼接 url 举行跳转
- har 中的代码和资源跟随使用方编译,如果有多个使用方,存在多个 hap,也包罗使用了 hsp 的场景。该 har 的编译产物中会存在多份雷同拷贝。
存在两种情况,如果harA和harB都依赖harC,单个hap依赖harA、harB,那么只会存在一份harA、harB、harC;如果harA和harB都依赖harC,有两个hap,hapA依赖harA,hapB依赖harB,那么终极会存在一份harA、harB,两份harC;
- hsp 中的代码和资源可以独立编译,运行时在一个进程中代码也只会存在一份
举个例子:
- 如果应用内没有使用动态包,大概把单例的封装放在动态包里被其他包(静态包或动态包)直接依赖,我们的单例可以这么写:
登录后复制 - class SimgleProvider {
- private static _instance?: SimgleProvider
- public static instance(): SimgleProvider {
- if (!SimgleProvider._instance) {
- SimgleProvider._instance = new SimgleProvider()
- }
- return SimgleProvider._instance
- }
- }
复制代码
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 在应用的任意一处获取这个单例都是同一个对象
- 但如果单例封装放在 har 包内,该 har 包被其他包(静态包和动态包)依赖引用。仍然使用上述代码实现单例,我们在 entry 大概 被 entry 直接依赖的 har 中获取到的 SimgleProvider 单例 和在 hsp 包内获取到的 SimgleProvider 单例 不是指向同一份内存,它会被拷贝两份。
可以简单的总结为:一个应用内,同一个 har 包,如果同时被 har (大概entry)和 hsp 依赖引用,会被拷贝两份。
- 如果需要整个应用内,包罗 entry、har、hsp 都指向的是同一个单例对象,要么把这个单例封装放在 hsp 内,如果单例封装放在 har 内,需要把上面的单例实现代码改成如下:
登录后复制 - class SimgleProvider {
- private static _instance?: SimgleProvider
- public static instance(): FastRouter {
- const storageKey = 'SimgleProvider'
- if (!AppStorage.has(storageKey)) {
- AppStorage.setOrCreate(storageKey, new SimgleProvider())
- }
- return AppStorage.get<SimgleProvider>(storageKey)!
- }
- }
复制代码
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
上述示例demo已上传,请参考下方链接
附注(Example)
Demo 示例已上传:
GitHub: https://github.com/liyufengrex/HarmonyAtomicService
GitCode: https://gitcode.com/liyufengrex/HarmonyAtomicService
(基于API11开发,支持NEXT及以上版本运行)已上传可供参考,包罗如下内容:
- 静态库+动态包+多模块设计
- 状态管理
- 统一起由管理(router+navPathStack)
- 网络请求、Loading、Toast、数据长期化 等工具库封装
- 自定义组件、自定义弹窗(解耦)
- EventBus 事件通知
- 扩展修饰器,实现 节省、防抖、权限申请
- 动态路由 (navPathStack + 动态import + WrappedBuilder)
- UI动态节点操作 (BuilderNode + NodeController)
- 折叠屏适配示例
- 组件工厂示例
- 组件动态属性设置示例
登录后复制 - // 工程目录
- ├──entry // ets代码区
- │ └──src/main/ets
- │ ├──entryability
- │ │ ├──FoldStatusObserver.ets // 折叠屏幕变化监听
- │ │ └──EntryAbility.ets
- │ ├──pages
- │ │ └──MainPage.ets // 首页
- ├──business // 放置静态包的文件夹(业务模块)
- │ ├──feature_home // 放置首页Tab里的一些示例页面
- │ │ └──/src/main/ets/pages
- │ │ ├──HomePage.ets //首页的第一个Tab
- │ │ ├──BuilderNodeExample.ets //动态节点操作示例
- │ │ ├──CustomDialogExample.ets //自定义弹窗解耦
- │ │ ├──EventBusExample.ets //消息通知
- │ │ ├──HttpRequestExample.ets //网络请求示例
- │ │ ├──PermissionExample.ets //使用注解请求权限
- │ │ ├──RouterCallbackExample.ets //使用 NavPathStack 与 Route 两种方式实现页面跳转及回调(HSP、HAR)
- │ │ ├──FixFoldUiExample.ets //折叠屏适配示例
- │ │ ├──ComponentFactoryExample.ets //组件工厂示例
- │ │ ├──AttributeModifierExample.ets //组件动态属性设置示例
- │ │ └──ThrottleExample.ets //使用注解防抖
- │ ├──feature_setting
- │ │ └──/src/main/ets/pages
- │ │ ├──SettingPages.ets //首页的第二个Tab
- │ │ └──TestDynamicNavPage.ets //测试动态路由示例
- ├──features //放置动态包的文件夹
- │ ├──feature_has_page
- │ │ └──/src/main/ets/pages
- │ │ ├──TestHspNavPathPage.ets //测试 NavPath 跳转 HSP 内页面
- │ │ └──TestHspRouterPage.ets //测试 Route 跳转 HSP 内页面
- ├──base
- │ ├──fast_ui //封装公共UI
- │ │ ├──/src/main/ets/compnents
- │ │ │ ├──FoldStatusContainer.ets // 折叠屏变化响应组件封装
- │ │ │ ├──FastLoading.ets // loading工具
- │ │ │ └──FastToast.ets // toast工具
- │ │ └──/src/main/ets/styles // 公共样式
- │ ├──fast_util // 通用工具
- │ │ ├──/src/main/ets
- │ │ ├──EventBus.ets // 消息通知+监听
- │ │ ├──FastLog.ets // 日志打印
- │ │ ├──FastNavRouter.ets // 用于动态路由
- │ │ ├──FastPermission.ets // 请求权限注解器
- │ │ ├──FastRouter.ets // 基于 router 库封装,为了实现页面回调
- │ │ ├──FastTool.ets
- │ │ ├──PreferencesUtil.ets // 数据持久化工具
- │ │ └──ThrottleTool.ets // 防抖注解器
- │ ├──global_constant
- │ │
- ├──entry/src/main/resources // 应用资源目录
- └──module.json5 // 添加卡片拓展能力
复制代码
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
补充
从 API version 12 开始,Navigation支持使用系统路由表的方式举行动态路由。各业务模块(HSP/HAR)中需要独立配置router_map.json文件(可参考上述demo内TestHspNavPathPage.ets文件)。
具体可参考文档: Navigation系统路由
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |