路由的选择
HarmonyOS 提供两种路由实现的方式,分别是 Router 和 NavPatchStack。两者使用场景和特效各有优劣。
如果是单包应用开发,不使用动态包(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 {
// 默认空页面
}
}
复制代码
/// 任意一个页面获取 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()
})
}
}
复制代码
/// 目标页面接收入参、并返回页面回调
@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('跳转目标页')
}
}
复制代码
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 })
复制代码
重点:截止 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()
}
}
复制代码
任一页面使用 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)
复制代码
在目标页面内接收入参并回调结果
@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()
})
}
}
复制代码
总结
NavPatchStack 和 Router 两种路由方式各有优劣,NavPatchStack 方便统一管理,Router 方便解耦,两者没有任何关联,可以一起使用,也可以单独使用。
扩展:动态包、静态包的使用差异
说到动态包(HAR)和静态包(HSP),这里扩展一下两者的区别。
静态包的 module.json5 文件,type 标识为 har
{
"module": {
"name": "静态包模块名称",
"type": "har",
"deviceTypes": [
"default",
"tablet"
]
}
}
复制代码
静态包的 module.json5 文件,type 标识为 shared
{
"module": {
"name": "动态包模块名称",
"type": "shared",
"description": "$string:shared_desc",
"deviceTypes": [
"phone",
"tablet"
],
"deliveryWithInstall": true,
"installationFree": true,
"pages": "$profile:main_pages"
}
}
复制代码
动态包和静态包都可以被直接引用,在 oh-package.json5
内
{
...省略
"dependencies": {
"@rex/任意名称": "file:../../base/静态包模块名称",
"@rex/任意名称": "file:../../base/动态包模块名称"
}
}
复制代码
重点:
存在两种情况,如果 harA 和 harB 都依赖 harC,单个 hap 依赖 harA、harB,那么只会存在一份 harA、harB、harC;如果 harA 和 harB 都依赖 harC,有两个 hap,hapA 依赖 harA,hapB 依赖 harB,那么最终会存在一份 harA、harB,两份 harC;
举个例子:
class SimgleProvider {
private static _instance?: SimgleProvider
public static instance(): SimgleProvider {
if (!SimgleProvider._instance) {
SimgleProvider._instance = new SimgleProvider()
}
return SimgleProvider._instance
}
}
复制代码
可以简单的总结为:一个应用内,同一个 har 包,如果同时被 har (或者 entry)和 hsp 依赖引用,会被拷贝两份。
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)!
}
}
复制代码
上述示例 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 // 添加卡片拓展能力
复制代码
补充
从 API version 12 开始,Navigation 支持使用系统路由表的方式进行动态路由。各业务模块(HSP/HAR)中需要独立配置 router_map.json 文件(可参考上述 demo 内TestHspNavPathPage.ets
文件)。
具体可参考文档:Navigation系统路由
评论