@fw/router是在HarmonyOS鸿蒙系统中应用开发所使用的路由组件,该路由组件基于模块化开发思想设计,与系统路由相比使用更便捷,功能更丰富。
api版本:api12
功能限制:
dynamicImport
方法;ohpm install @fw/router
使用router模式管理页面时,处理页面返回值依赖UIAbility对象,需要对路由组件进行初始化。
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
RouterManager.getInstance().init(this);
}
}
遵循router组件定义页面路由的逻辑:
@Entry({ routeName: "demoPage" })
@Component
export struct DemoPage {
build() {
Column() {
}
}
}
在组件对外Index.ets(组件项目oh-package.json5下面的main对应文件,没有就创建)中export页面。
export { DemoPage } from './src/main/ets/pages/DemoPage'
注意: routeName须保持唯一,同时须符合鸿蒙页面别名规范。
RouterManager.getInstance().openWithRequest({
url: 'libraryHarDemo/demoPage?key=www',
params: { 'from': 'libraryHar-TestPage' }
})
调用路由时使用动态导入能力,需要添加配置,参照开启动态导入能力。 使用路由的其他问题见路由打开相关问题。
因为Entry中的页面不支持设置命名路由,RouterManager
支持用页面path打开。
RouterManager.getInstance()
.openWithRequest({
url: 'pages/SecondPage',
params: { "from" : "libraryHar-TestPage" } as Record<string, ESObject>
})
使用router.getParams()
获取参数。
@Entry({ routeName: "demoPage" })
@Component
export struct DemoPage {
@Prop params?: Record<string, ESObject>
aboutToAppear(): void {
if (router.getParams()) {
this.params = router.getParams() as Record<string, string>;
}
}
}
关闭页面支持返回上一页和返回指定页面。
// 返回上一页
RouterManager.getInstance().back({params: {
info: `${this.pageName} back`,
data: `${this.pageName} back data`
}})
// 返回指定页面
RouterManager.getInstance()
.back({url:"libraryHar/testPage", params:{
info: `${this.pageName} back testPage`,
data: `${this.pageName} back testPage data`
}})
openWithRequest
方法的返回值是Promise<RouterResponse>
,直接用.then
获取上一页的返回数据。
RouterManager.getInstance()
.openWithRequest({
url: 'libraryHarDemo/returnResultPage',
}).then((response) => {
if (response.code == RouterResponseError.Success.code) {
const p = response.data as Record<string, string>;
promptAction.showDialog({ message: JSON.stringify(p) })
}
})
使用Navigation模式,需要用户自己在业务根视图中嵌套Navigation,并和RouterManager保持联通。
如果没有特殊要求可以直接在业务根视图中嵌套@fw/router
提供的FWNavigation
组件。
@Entry
@Component
struct Index {
build() {
Row() {
FWNavigation() {
Column() {
// 业务代码
}
.width('100%')
}
}
.height('100%')
}
}
若有其他需要耦合Navigation组件的逻辑,可以参考FWNavigation
组件代码自己实现相关逻辑。
定义Navigation页面时,需要嵌套NavDestination
组件。
@Component
export struct DemoDestination {
@Prop params?: Record<string, ESObject>
build() {
Column() {
NavDestination() {
DemoPageContent({ pageName: 'DemoDestination', params: this.params })
}
}
}
}
DemoDestination
的自动注册。
1.添加自定义注解
给DemoDestination
添加自定义注解NavigationRoute
设置路由名称,如果包含参数需要设置hasParams
为true。
@NavigationRoute({ routeName: "demoPage", hasParams: true })
@Component
export struct DemoDestination {
}
2.集成hvigor插件
在工程的hvigor/hvigor-config.json5
文件中添加插件依赖:
{
"dependencies": {
"@ohos/hvigor-ohos-plugin": "4.1.2",
"fw-router-hvigor-plugin": "1.0.0"
},
}
在对应的模块hvigorfile.ts
中引用路由提供的插件。
import { harTasks } from '@ohos/hvigor-ohos-plugin';
import { routerHvigorPlugin } from 'fw-router-hvigor-plugin';
export default {
system: harTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
plugins:[
routerHvigorPlugin()
] /* Custom plugin to extend the functionality of Hvigor. */
}
3.项目编译运行运行
routerHvigorPlugin
插件会自动扫描本模块代码中的NavigationRoute
装饰器,自动生成代码写入模块的/src/main/ets/generated/RouterBuilder.ets
文件。
如果当前module是har包,会自动将之添加到Index.ets
文件export:
export * from './src/main/ets/generated/RouterBuilder';
之后,当触发动态导入模块时,自动生成的代码也会自动导入从而触发自定义装饰器逻辑完成自动注册。
如果当前module是hap包,会自动将之添加到EntryAbility.ets
文件import。
import('../generated/RouterBuilder');
Navigation模式的调用方式与系统router模式一致。
RouterManager.getInstance().openWithRequest({
url: 'libraryHarDemo/demoPage?key=www',
params: { 'from': 'libraryHar-TestPage' }
})
调用路由时使用动态导入能力,需要添加配置,参照开启动态导入能力。 使用路由的其他问题见路由打开相关问题。
页面定义自动处理时,自定义注解NavigationRoute
设置hasParams
为true,并定义@State params
用来接收参数即可。
@NavigationRoute({ routeName: "demoPage", hasParams: true })
@Component
export struct DemoDestination {
@State params: Record<string, string> = {}
}
Navigation模式的关闭页面回传数据方式与router一致。
// 返回上一页
RouterManager.getInstance().back({params: {
info: `${this.pageName} back`,
data: `${this.pageName} back data`
}})
// 返回指定页面
RouterManager.getInstance()
.back({url:"libraryHar/testPage", params:{
info: `${this.pageName} back testPage`,
data: `${this.pageName} back testPage data`
}})
Navigation模式的获取页面返回值方式与router一致。
RouterManager.getInstance()
.openWithRequest({
url: 'libraryHarDemo/returnResultPage',
}).then((response) => {
if (response.code == RouterResponseError.Success.code) {
const p = response.data as Record<string, string>;
promptAction.showDialog({ message: JSON.stringify(p) })
}
})
import { ServiceRoute, Route, SuccessCallback, ErrorCallback } from '@fw/router'
@Route({ routeName: "testService" })
export class TestServiceRoute extends ServiceRoute {
onAction(pageInstance: ESObject, params: Record<string,ESObject>, callback: SuccessCallback, errorCallback?: ErrorCallback) {
callback({ name: "ZhangSan" })
}
}
onAction
方法的params
参数即为路由调用的入参。
在组件Index.ets
(没有就创建)中export。
export { TestServiceRoute } from './src/main/ets/service/TestServiceRoute'
调用服务路由、传参、获取返回值和页面路由使用同样的api。
RouterManager.getInstance().openWithRequest({
url: 'libraryHarDemo/testService',
params: { 'from': 'libraryHar-TestPage' }
}).then((response) => {
promptAction.showDialog({ message: JSON.stringify(response?.data) })
})
调用路由时使用动态导入能力,需要添加配置,参照开启动态导入能力。 使用路由的其他问题见路由打开相关问题。
页面路由和服务路由定义时只需要指定名称(比如demoPage
或testService
),但是调用路由时需要在名称之前增加其所属的包名(例如libraryHarDemo/demoPage
或libraryHarDemo/testService
)。
当然,你也可以传递一个完整的url字符串app://base/libraryHarDemo/demoPage
,目前路由组件支持该样式,但没有使用url中的scheme和host部分。
打开页面路由默认是push模式,如果想要replace,可以指定openMode参数。
RouterManager.getInstance().openWithRequest({
originPath: 'libraryHarDemo/demoPage?key=www',
params: { 'from': 'libraryHar-TestPage' },
openMode: PageRouteOpenMode.replace
})
router和Navigation都支持replace操作。
解释:
动态导入是指应用开始运行时不import页面路由或者服务路由所在的har包,而是在业务代码调用指定路由时,先import指定har包,然后再执行相关代码。
修改entry/build-profile.json5
,增加相关设置:
{
"buildOption": {
"arkOptions": {
"runtimeOnly": {
"sources": [],
"packages": [
"@fw/router",
'libraryHar',
"libraryHarDemo",
]
}
}
},
}
1.目前路由组件默认是开启动态导入能力的,不过目前默认是动态导入整个har包;
2.如果想要更细的控制粒度,比如按页面动态导入,需要实现RouterManager.getInstance().delegate
的dynamicImport
方法,自定义动态导入逻辑;
3.如果是本地har包,动态导入逻辑正常;而远程依赖har包,在api11/api12上无法成功,也需要实现RouterManager.getInstance().delegate
的dynamicImport
方法,在业务代码撰写import()
方法;
RouterManager.getInstance().delegate = {
dynamicImport:(packageName: string, request: RouterRequestWrapper): Promise<ESObject> => {
return import(packageName)
}
}
4.如果不需要动态导入逻辑,可以直接关闭;该状态下,需要自己在业务代码中导入har包或者指定文件;
RouterManager.getInstance().enableDynamicImport = false;
动态导入功能依赖path格式,{moduleName}/.../{pageName}
,动态导入目前是解析路由名第一个/字符之前的部分作为包名进行导入;
如果是全新开发应用,只需要将路由定义中的moduleName和har包名称保持一致。
但是如果路由名称需要和以前开发的iOS/Android平台应用保持,那路由中包含的moduleName可能就无法和har包保持一致。
路由组件提供了模块名-包名的映射关系,可以自由设置某个har包中支持moduleName。
RouterManager.getInstance().setModuleToPackageMapping('demo', 'libraryHarDemo')
RouterManager.getInstance()
.openWithRequest({
originPath: 'demo/oldService'
}).then((response) => {
promptAction.showDialog({ message: JSON.stringify(response?.data) })
})
注意:
moduleName对packageName是多对一关系;因此一个moduleName只能存在于一个har包中。
如果因为某些原因,业务确实存在一个moduleName存在多个har包中的情况,只能关闭动态导入功能或者自定义动态导入逻辑。
页面路由的router模式和Navigation模式可以混合使用。
router页面和Navigation页面混合使用时,页面定义方式以及获取入参的方式与单独使用router或单独使用Navigation时相同。
但是每一个router的页面都需要嵌入Navigation,并且和RouterManager联动。
可以使用路由组件封装了FWNavigation
组件,也可以参照其代码自己实现。
@Entry({ routeName: "demoPage" })
@Component
export struct DemoPage {
@Prop params?: Record<string, ESObject>
aboutToAppear(): void {
if (router.getParams()) {
this.params = router.getParams() as Record<string, string>;
}
}
build() {
Column() {
// 此处由系统Navigation组件替换为FWNavigation组件
FWNavigation() {
DemoPageContent({ pageName: 'DemoPage', params: this.params })
}
}
}
}
路由组件提供了属性,以修改打开页面路由的策略:
RouterManager.sInstance.routerStrategy = RouterStrategy.navigationFirst
/**
* 页面路由打开策略
*/
export enum RouterStrategy {
/**
* 优先NavPathStack打开页面。只有当NavPathStack无法响应时,才尝试用router打开页面。
*/
navigationFirst,
/**
* 优先用router打开页面。只有当router无法响应时,才尝试NavPathStack打开页面。
*/
routerFirst,
/**
* 只用NavPathStack打开页面。
*/
navigationOnly,
/**
* 只用router打开页面。
*/
routerOnly,
}
正常情况下,用户想要使用单独的router模式或者单独的Navigation模式,不需要关心routerStrategy属性,只需要按照文档定义对应页面即可。
在混合模式下,RouterStrategy
的navigationFirst
和routerFirst
可能会影响具体的效果。
当一个页面路由同时存在router类型页面和Navigation类型页面时,navigationFirst
会首先尝试用Navigation打开页面,如果无法打开,则使用router打开;routerFirst
会首先尝试用router打开页面,如果无法打开,则使用Navigation打开。
在单独router模式和单独Navigation模式中,back方法的toUrl参数没有限制。
但是在混合模式中,当前router中返回时,无法返回到之前router内嵌套的Navigation页面中,只能返回到当前router中内嵌的Navigation页面或者router页面。
自定义拦截器类,实现RouterInterceptor
接口。
接口支持三个方法,分别对应不同的场景。
export interface RouterInterceptor {
/**
* 路由打开操作。
* @param request 路由请求,注意参数是经过处理的RouterRequestWrapper类型。
* @returns 是否拦截,如果返回true,则路由不会继续原处理逻辑。
*/
open?(request: RouterRequestWrapper): Promise<boolean>
/**
* 关闭页面操作。
* @param options 关闭页面参数,注意参数是经过处理的RouterBackOptionsWrapper类型。
* @returns 是否拦截,如果返回true,则路由不会继续原处理逻辑。
*/
close?(options?: RouterBackOptionsWrapper): boolean
/**
* 收到响应数据。
*
* 需要对错误做统一处理或者对特殊错误拦截请实现该方法。
* @param request 路由请求对象。
* @param response 路由返回对象。
* @returns 是否拦截,如果返回true,则路由不会继续原处理逻辑。
*/
onResponse?(request: RouterRequestWrapper, response: RouterResponse): Promise<boolean>
}
this.webPageInterceptor = new WebPageInterceptor()
RouterManager.getInstance().registerInterceptor(this.webPageInterceptor)
RouterManager.getInstance().unregisterInterceptor(this.webPageInterceptor)
打开路由需要登录或者有其他权限限制时,可以使用该方案。
单纯的路由重定向需求,比如原生页面降级为H5页面,H5页面重定向到别的H5页面,也可以参考该逻辑。配合网络配置即可完成动态重定向。
export class WebPageInterceptor implements RouterInterceptor {
open(request: RouterRequestWrapper): Promise<boolean> {
return new Promise<boolean>((resolve, reject) => {
if (request.params['needLogin'] as boolean && !AccountManager.instance.isLogin) {
// 打开登录页面,并传入原路由地址。登录成功后会自动跳转原路由地址。
RouterManager.getInstance().openWithRequest({ url: 'login/loginPage', params: { redirectUrl: request.rawRequest.url } })
resolve(true)
} else {
resolve(false)
}
})
}
}
当需要支持不同类型的url时,可以使用拦截器然后分发到对应的处理逻辑中。这样可以做到调用入口统一。
下面是让RouterManager支持直接打开http地址(https://www.baidu.com)的处理逻辑。
export class WebPageInterceptor implements RouterInterceptor {
open(request: RouterRequestWrapper): Promise<boolean> {
return new Promise<boolean>((resolve, reject) => {
if (request.rawRequest.url.startsWith('http')) {
RouterManager.getInstance().openWithRequest({ url: 'pages/MockWebPage', params: { url: request.rawRequest.url } })
resolve(true)
} else {
resolve(false)
}
})
}
}
对于未实现的路由,如果想要定义一个统一的缺省页面,可以使用该方式。
export class PageFallBackInterceptor implements RouterInterceptor {
onResponse(request: RouterRequestWrapper, response: RouterResponse): Promise<boolean> {
return new Promise<boolean>((resolve, reject) => {
if (response.code == RouterResponseError.RequestNotFoundResponsor.code) {
RouterManager.getInstance().openWithRequest({ url: 'pages/PageNotFoundPage', params: request })
resolve(true)
} else {
resolve(false)
}
})
}
}
需要对于路由的错误返回有统一处理,showToast或者日志,可以参照该方案。
export class RouterFailLogInterceptor implements RouterInterceptor {
onResponse(request: RouterRequestWrapper, response: RouterResponse): Promise<boolean> {
return new Promise<boolean>((resolve, reject) => {
if (response.code != RouterResponseError.Success.code) {
console.log(`路由请求[${request.uri}]报错,错误code=${response.code}, msg=${response.msg}`)
}
resolve(false)
})
}
}
使用Navigation页面模式时支持自定义Dialog对话框。
下面是Alert对话框的演示代码:
@NavigationRoute({ routeName: "alert", hasParams: true })
@Component
export struct AlertOnlyNavigation {
@Prop params?: Record<string, ESObject>
build() {
Column() {
NavDestination() {
AlertContent({ params: this.params })
}
.mode(NavDestinationMode.DIALOG)
}
}
}
@Component
export struct AlertContent {
@Prop params: Record<string, ESObject>
build() {
Column() {
Row() {
Column() {
Text(this.params['title'])
Text(this.params['message'])
.margin({top: 16})
Row() {
Button('取消')
.layoutWeight(1)
.onClick(() => {
RouterManager.getInstance().back({ params: { 'action': '取消' } })
})
Button('确定')
.layoutWeight(1)
.onClick(() => {
RouterManager.getInstance().back({ params: { 'action': '确定' } })
})
}
.margin({top: 16})
}
.margin({left: 16, right: 16})
.padding(16)
.layoutWeight(1)
.backgroundColor(Color.White)
.borderRadius(10)
}
.height('100%')
}
.width('100%')
.backgroundColor('#33000000')
}
}
打开Alert的方式与路由相同。
RouterManager.getInstance().openWithRequest({
url: 'libraryHarDemo/alert',
params: { 'title': '温馨提示', 'message': '测试文本问问本' }
}).then((response) => {
if (response.code == RouterResponseError.Success.code) {
promptAction.showDialog({ message: `你点击了 ${response.data.action}` })
}
})
可以对打开Alert的方法进行二次封装。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。