1# 页面路由 (@ohos.router)(不推荐) 2<!--Kit: ArkUI--> 3<!--Subsystem: ArkUI--> 4<!--Owner: @mayaolll--> 5<!--Designer: @jiangdayuan--> 6<!--Tester: @lxl007--> 7<!--Adviser: @HelloCrease--> 8 9 10页面路由指在应用程序中实现不同页面之间的跳转和数据传递。Router模块通过不同的url地址,可以方便地进行页面路由,轻松地访问不同的页面。本文将从[页面跳转](#页面跳转)、[页面返回](#页面返回)、[页面返回前增加一个询问框](#页面返回前增加一个询问框)和[命名路由](#命名路由)这几个方面,介绍如何通过Router模块实现页面路由。 11 12>**说明:** 13> 14>[组件导航 (Navigation)](./arkts-navigation-navigation.md)具有更强的功能和自定义能力,推荐使用该组件作为应用的路由框架。Navigation和Router的差异可参考[Router切换Navigation](./arkts-router-to-navigation.md)指导。 15 16## 页面跳转 17 18页面跳转是开发过程中的一个重要组成部分。在使用应用程序时,通常需要在不同的页面之间跳转,有时还需要将数据从一个页面传递到另一个页面。 19 20 **图1** 页面跳转 21 22 23Router模块提供了两种跳转模式,分别是[pushUrl](../reference/apis-arkui/arkts-apis-uicontext-router.md#pushurl)和[replaceUrl](../reference/apis-arkui/arkts-apis-uicontext-router.md#replaceurl)。这两种模式决定了目标页面是否会替换当前页。 24 25- pushUrl:目标页面不会替换当前页,而是压入页面栈。这样可以保留当前页的状态,并且可以通过返回键或者调用[back](../reference/apis-arkui/arkts-apis-uicontext-router.md#back)方法返回到当前页。 26 27- replaceUrl:目标页面会替换当前页,并销毁当前页。这样可以释放当前页的资源,并且无法返回到当前页。 28 29>**说明:** 30> 31>- 创建新页面时,请参考<!--RP1-->[构建第二个页面](../quick-start/start-with-ets-stage.md#构建第二个页面)<!--RP1End-->配置第二个页面的路由。 32> 33> 34>- 页面栈的最大容量为32个页面。如果超过这个限制,可以调用[clear](../reference/apis-arkui/arkts-apis-uicontext-router.md#clear)方法清空历史页面栈,释放内存空间。 35 36同时,Router模块提供了两种实例模式,分别是Standard和Single。这两种模式决定了目标url是否会对应多个实例。 37 38- Standard:多实例模式,也是默认情况下的跳转模式。目标页面会被添加到页面栈顶,无论栈中是否存在相同url的页面。 39 40- Single:单实例模式。如果目标页面的url已经存在于页面栈中,则会将离栈顶最近的同url页面移动到栈顶,该页面成为新建页。如果目标页面的url在页面栈中不存在同url页面,则按照默认的多实例模式进行跳转。 41 42- 场景一:有一个主页(Home)和一个详情页(Detail),希望从主页点击一个商品,跳转到详情页。同时,需要保留主页在页面栈中,以便返回时恢复状态。这种场景下,可以使用pushUrl方法,并且使用Standard实例模式(或者省略)。 43 44 ```ts 45 // 在Home页面中 46 onJumpClick(): void { 47 this.getUIContext().getRouter().pushUrl({ 48 url: 'pages/Detail' // 目标url 49 }, router.RouterMode.Standard, (err) => { 50 if (err) { 51 console.error(`Invoke pushUrl failed, code is ${err.code}, message is ${err.message}`); 52 return; 53 } 54 console.info('Invoke pushUrl succeeded.'); 55 }); 56 } 57 ``` 58 59 >**说明:** 60 > 61 >多实例模式下,router.RouterMode.Standard参数可以省略。 62 63- 场景二:有一个登录页(Login)和一个个人中心页(Profile),希望从登录页成功登录后,跳转到个人中心页。同时,销毁登录页,在返回时直接退出应用。这种场景下,可以使用replaceUrl方法,并且使用Standard实例模式(或者省略)。 64 65 ```ts 66 // 在Login页面中 67 onJumpClick(): void { 68 this.getUIContext().getRouter().replaceUrl({ 69 url: 'pages/Profile' // 目标url 70 }, router.RouterMode.Standard, (err) => { 71 if (err) { 72 console.error(`Invoke replaceUrl failed, code is ${err.code}, message is ${err.message}`); 73 return; 74 } 75 console.info('Invoke replaceUrl succeeded.'); 76 }) 77 } 78 ``` 79 80 >**说明:** 81 > 82 >多实例模式下,router.RouterMode.Standard参数可以省略。 83 84- 场景三:有一个设置页(Setting)和一个主题切换页(Theme),希望从设置页点击主题选项,跳转到主题切换页。同时,需要保证每次只有一个主题切换页存在于页面栈中,在返回时直接回到设置页。这种场景下,可以使用pushUrl方法,并且使用Single实例模式。 85 86 ```ts 87 // 在Setting页面中 88 onJumpClick(): void { 89 this.getUIContext().getRouter().pushUrl({ 90 url: 'pages/Theme' // 目标url 91 }, router.RouterMode.Single, (err) => { 92 if (err) { 93 console.error(`Invoke pushUrl failed, code is ${err.code}, message is ${err.message}`); 94 return; 95 } 96 console.info('Invoke pushUrl succeeded.'); 97 }); 98 } 99 ``` 100 101- 场景四:有一个搜索结果列表页(SearchResult)和一个搜索结果详情页(SearchDetail),希望从搜索结果列表页点击某一项结果,跳转到搜索结果详情页。同时,如果该结果已经被查看过,则不需要再新建一个详情页,而是直接跳转到已经存在的详情页。这种场景下,可以使用replaceUrl方法,并且使用Single实例模式。 102 103 ```ts 104 // 在SearchResult页面中 105 onJumpClick(): void { 106 this.getUIContext().getRouter().replaceUrl({ 107 url: 'pages/SearchDetail' // 目标url 108 }, router.RouterMode.Single, (err) => { 109 if (err) { 110 console.error(`Invoke replaceUrl failed, code is ${err.code}, message is ${err.message}`); 111 return; 112 } 113 console.info('Invoke replaceUrl succeeded.'); 114 }) 115 } 116 ``` 117 118以上是不带参数传递的场景。 119 120如果需要在跳转时传递一些数据给目标页面,则可以在调用Router模块的方法时,添加一个params属性,并指定一个对象作为参数。例如: 121 122 123```ts 124class DataModelInfo { 125 age: number = 0; 126} 127 128class DataModel { 129 id: number = 0; 130 info: DataModelInfo | null = null; 131} 132 133onJumpClick(): void { 134 // 在Home页面中 135 let paramsInfo: DataModel = { 136 id: 123, 137 info: { 138 age: 20 139 } 140 }; 141 142 this.getUIContext().getRouter().pushUrl({ 143 url: 'pages/Detail', // 目标url 144 params: paramsInfo // 添加params属性,传递自定义参数 145 }, (err) => { 146 if (err) { 147 console.error(`Invoke pushUrl failed, code is ${err.code}, message is ${err.message}`); 148 return; 149 } 150 console.info('Invoke pushUrl succeeded.'); 151 }) 152} 153``` 154 155在目标页面中,可以通过调用Router模块的[getParams](../reference/apis-arkui/arkts-apis-uicontext-router.md#getparams)方法来获取传递过来的参数。例如: 156 157 158```ts 159class InfoTmp { 160 age: number = 0; 161} 162 163class RouTmp { 164 id: object = () => { 165 }; 166 info: InfoTmp = new InfoTmp(); 167} 168 169const params: RouTmp = this.getUIContext().getRouter().getParams() as RouTmp; // 获取传递过来的参数对象 170const id: object = params.id; // 获取id属性的值 171const age: number = params.info.age; // 获取age属性的值 172``` 173 174 175## 页面返回 176 177当用户在一个页面完成操作后,通常需要返回到上一个页面或者指定页面,这就需要用到页面返回功能。在返回的过程中,可能需要将数据传递给目标页面,这就需要用到数据传递功能。 178 179 **图2** 页面返回 180 181 182 183直接使用router可能导致[UI上下文不明确](./arkts-global-interface.md#ui上下文不明确)的问题,建议使用getUIContext()获取[UIContext](../reference/apis-arkui/arkts-apis-uicontext-uicontext.md)实例,并使用[getRouter](../reference/apis-arkui/arkts-apis-uicontext-uicontext.md#getrouter)获取绑定实例的router。 184 185可以使用以下几种方式返回页面: 186 187- 方式一:返回到上一个页面。 188 189 190 ```ts 191 this.getUIContext().getRouter().back(); 192 ``` 193 194 这种方式会返回到上一个页面,即上一个页面在页面栈中的位置。但是,上一个页面必须存在于页面栈中才能够返回,否则该方法将无效。 195 196- 方式二:返回到指定页面。 197 198 199 返回普通页面。 200 201 ```ts 202 this.getUIContext().getRouter().back({ 203 url: 'pages/Home' 204 }); 205 ``` 206 207 返回命名路由页面。 208 209 ```ts 210 this.getUIContext().getRouter().back({ 211 url: 'myPage' // myPage为返回的命名路由页面别名 212 }); 213 ``` 214 215 这种方式可以返回到指定页面,需要指定目标页面的路径。目标页面必须存在于页面栈中才能够返回。 216 217- 方式三:返回到指定页面,并传递自定义参数信息。 218 219 220 返回到普通页面。 221 222 ```ts 223 this.getUIContext().getRouter().back({ 224 url: 'pages/Home', 225 params: { 226 info: '来自Home页' 227 } 228 }); 229 ``` 230 231 返回命名路由页面。 232 233 ```ts 234 this.getUIContext().getRouter().back({ 235 url: 'myPage', // myPage为返回的命名路由页面别名 236 params: { 237 info: '来自Home页' 238 } 239 }); 240 ``` 241 242 这种方式不仅可以返回到指定页面,还可以在返回的同时传递自定义参数信息。这些参数信息可以在目标页面中通过调用[getParams](../reference/apis-arkui/arkts-apis-uicontext-router.md#getparams)方法进行获取和解析。 243 244在目标页面中,在需要获取参数的位置调用[getParams](../reference/apis-arkui/arkts-apis-uicontext-router.md#getparams)方法即可,例如在[onPageShow](../reference/apis-arkui/arkui-ts/ts-custom-component-lifecycle.md#onpageshow)生命周期回调中: 245 246> **说明:** 247> 248> 直接使用router可能导致[UI上下文不明确](./arkts-global-interface.md#ui上下文不明确)的问题,建议使用getUIContext()获取[UIContext](../reference/apis-arkui/arkts-apis-uicontext-uicontext.md)实例,并使用[getRouter](../reference/apis-arkui/arkts-apis-uicontext-uicontext.md#getrouter)获取绑定实例的router。 249 250```ts 251@Entry 252@Component 253struct Home { 254 @State message: string = 'Hello World'; 255 256 onPageShow() { 257 const params = this.getUIContext().getRouter().getParams() as Record<string, string>; // 获取传递过来的参数对象 258 if (params) { 259 const info: string = params.info as string; // 获取info属性的值 260 } 261 } 262 263 // ... 264} 265``` 266 267>**说明:** 268> 269>当使用back方法返回到指定页面时,原栈顶页面(包括)到指定页面(不包括)之间的所有页面栈都将从栈中弹出并销毁。 270> 271> 另外,如果使用back方法返回到原来的页面,原页面不会被重复创建,因此使用\@State声明的变量不会重复声明,也不会触发页面的aboutToAppear生命周期回调。如果需要在原页面中使用返回页面传递的自定义参数,可以在需要的位置进行参数解析。例如,在onPageShow生命周期回调中进行参数解析。 272 273## 生命周期 274 275[router](../reference/apis-arkui/js-apis-router.md)页面生命周期,即被[\@Entry](state-management/arkts-create-custom-components.md#entry)装饰的组件生命周期,提供以下生命周期接口: 276 277- [onPageShow](../reference/apis-arkui/arkui-ts/ts-custom-component-lifecycle.md#onpageshow):页面每次显示时触发一次,包括路由过程、应用进入前台等场景。 278 279- [onPageHide](../reference/apis-arkui/arkui-ts/ts-custom-component-lifecycle.md#onpagehide):页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景。 280 281- [onBackPress](../reference/apis-arkui/arkui-ts/ts-custom-component-lifecycle.md#onbackpress):当用户点击返回按钮时触发。 282 283```ts 284// Index.ets 285@Entry 286@Component 287struct MyComponent { 288 // 只有被@Entry装饰的组件才可以调用页面的生命周期 289 onPageShow() { 290 console.info('Index onPageShow'); 291 } 292 293 // 只有被@Entry装饰的组件才可以调用页面的生命周期 294 onPageHide() { 295 console.info('Index onPageHide'); 296 } 297 298 // 只有被@Entry装饰的组件才可以调用页面的生命周期 299 onBackPress() { 300 console.info('Index onBackPress'); 301 // 返回true表示页面自己处理返回逻辑,不进行页面路由;返回false表示使用默认的路由返回逻辑,不设置返回值按照false处理 302 return true; 303 } 304 305 build() { 306 Column() { 307 // push到Page页面,执行onPageHide 308 Button('push to next page') 309 .onClick(() => { 310 this.getUIContext().getRouter().pushUrl({ url: 'pages/Page' }); 311 }) 312 } 313 } 314} 315``` 316```ts 317// Page.ets 318@Entry 319@Component 320struct Page { 321 @State textColor: Color = Color.Black; 322 @State num: number = 0; 323 324 // 只有被@Entry装饰的组件才可以调用页面的生命周期 325 onPageShow() { 326 console.info('Page onPageShow'); 327 this.num = 5; 328 } 329 330 // 只有被@Entry装饰的组件才可以调用页面的生命周期 331 onPageHide() { 332 console.info('Page onPageHide'); 333 } 334 335 // 只有被@Entry装饰的组件才可以调用页面的生命周期 336 onBackPress() { // 不设置返回值按照false处理 337 console.info('Page onBackPress'); 338 this.textColor = Color.Grey; 339 this.num = 0; 340 } 341 342 build() { 343 Column() { 344 Text(`num 的值为:${this.num}`) 345 .fontSize(30) 346 .fontWeight(FontWeight.Bold) 347 .fontColor(this.textColor) 348 .margin(20) 349 .onClick(() => { 350 this.num += 5; 351 }) 352 Button('pop to previous page') 353 .onClick(() => { 354 this.getUIContext().getRouter().back(); 355 }) 356 } 357 .width('100%') 358 } 359} 360``` 361 362 363## 页面返回前增加一个询问框 364 365在开发应用时,为了避免用户误操作或者丢失数据,有时候需要在用户从一个页面返回到另一个页面之前,弹出一个询问框,让用户确认是否要执行这个操作。 366 367本文将从[系统默认询问框](#系统默认询问框)和[自定义询问框](#自定义询问框)两个方面来介绍如何实现页面返回前增加一个询问框的功能。 368 369 **图3** 页面返回前增加一个询问框 370 371 372 373 374### 系统默认询问框 375 376为了实现这个功能,可以使用页面路由Router模块提供的两个方法:[showAlertBeforeBackPage](../reference/apis-arkui/arkts-apis-uicontext-router.md#showalertbeforebackpage)和[back](../reference/apis-arkui/arkts-apis-uicontext-router.md#back)来实现这个功能。 377 378直接使用router可能导致[UI上下文不明确](./arkts-global-interface.md#ui上下文不明确)的问题,建议使用getUIContext()获取[UIContext](../reference/apis-arkui/arkts-apis-uicontext-uicontext.md)实例,并使用[getRouter](../reference/apis-arkui/arkts-apis-uicontext-uicontext.md#getrouter)获取绑定实例的router。 379 380如果想要在目标界面开启页面返回询问框,需要在调用[back](../reference/apis-arkui/arkts-apis-uicontext-router.md#back)方法之前,通过调用[showAlertBeforeBackPage](../reference/apis-arkui/arkts-apis-uicontext-router.md#showalertbeforebackpage)方法设置返回询问框的信息。例如,在支付页面中定义一个返回按钮的点击事件处理函数: 381 382```ts 383import { BusinessError } from '@kit.BasicServicesKit'; 384 385// 定义一个返回按钮的点击事件处理函数 386function onBackClick(): void { 387 // 调用this.getUIContext().getRouter().showAlertBeforeBackPage方法,设置返回询问框的信息 388 try { 389 this.getUIContext().getRouter().showAlertBeforeBackPage({ 390 message: '您还没有完成支付,确定要返回吗?' // 设置询问框的内容 391 }); 392 } catch (err) { 393 let message = (err as BusinessError).message; 394 let code = (err as BusinessError).code; 395 console.error(`Invoke showAlertBeforeBackPage failed, code is ${code}, message is ${message}`); 396 } 397 398 // 调用this.getUIContext().getRouter().back()方法,返回上一个页面 399 this.getUIContext().getRouter().back(); 400} 401``` 402 403其中,this.getUIContext().getRouter().showAlertBeforeBackPage方法接收一个对象作为参数,该对象包含以下属性: 404 405message:string类型,表示询问框的内容。 406如果调用成功,则会在目标界面开启页面返回询问框;如果调用失败,则会抛出异常,并通过err.code和err.message获取错误码和错误信息。 407 408当用户点击“返回”按钮时,会弹出确认对话框,询问用户是否确认返回。选择“取消”将停留在当前页目标页面;选择“确认”将触发[back](../reference/apis-arkui/arkts-apis-uicontext-router.md#back)方法,并根据参数决定如何执行跳转。 409 410### 自定义询问框 411 412自定义询问框的方式,可以使用弹窗[showDialog](../reference/apis-arkui/arkts-apis-uicontext-promptaction.md#showdialog-1)或者自定义弹窗实现。这样可以让应用界面与系统默认询问框有所区别,提高应用的用户体验度。本文以弹窗为例,介绍如何实现自定义询问框。 413 414直接使用router可能导致[UI上下文不明确](./arkts-global-interface.md)的问题,建议使用getUIContext()获取[UIContext](../reference/apis-arkui/arkts-apis-uicontext-uicontext.md)实例,并使用[getRouter](../reference/apis-arkui/arkts-apis-uicontext-uicontext.md#getrouter)获取绑定实例的router。 415 416在事件回调中,调用弹窗的[showDialog](../reference/apis-arkui/arkts-apis-uicontext-promptaction.md#showdialog-1)方法: 417 418```ts 419import { promptAction} from '@kit.ArkUI'; 420import { BusinessError } from '@kit.BasicServicesKit'; 421 422onBackClick() { 423 // 弹出自定义的询问框 424 this.getUIContext().getPromptAction().showDialog({ 425 message: '您还没有完成支付,确定要返回吗?', 426 buttons: [ 427 { 428 text: '取消', 429 color: '#FF0000' 430 }, 431 { 432 text: '确认', 433 color: '#0099FF' 434 } 435 ] 436 }).then((result: promptAction.ShowDialogSuccessResponse) => { 437 if (result.index === 0) { 438 // 用户点击了“取消”按钮 439 console.info('User canceled the operation.'); 440 } else if (result.index === 1) { 441 // 用户点击了“确认”按钮 442 console.info('User confirmed the operation.'); 443 // 调用this.getUIContext().getRouter().back()方法,返回上一个页面 444 this.getUIContext().getRouter().back(); 445 } 446 }).catch((err: Error) => { 447 let message = (err as BusinessError).message; 448 let code = (err as BusinessError).code; 449 console.error(`Invoke showDialog failed, code is ${code}, message is ${message}`); 450 }) 451} 452``` 453 454当用户点击“返回”按钮时,会弹出自定义的询问框,询问用户是否确认返回。选择“取消”将停留在当前页目标页面;选择“确认”将触发[back](../reference/apis-arkui/arkts-apis-uicontext-router.md#back)方法,并根据参数决定如何执行跳转。 455 456## 命名路由 457 458在开发中为了跳转到共享包[HAR](../quick-start/har-package.md)或者[HSP](../quick-start/in-app-hsp.md)中的页面(即共享包中路由跳转),可以使用[pushNamedRoute](../reference/apis-arkui/arkts-apis-uicontext-router.md#pushnamedroute)来实现。 459 460 **图4** 命名路由跳转 461 462 463 464在使用页面路由Router相关功能之前,需要在代码中先导入Router模块。 465 466 467```ts 468import { router } from '@kit.ArkUI'; 469``` 470 471在想要跳转到的共享包[HAR](../quick-start/har-package.md)或者[HSP](../quick-start/in-app-hsp.md)页面里,给[@Entry](../ui/state-management/arkts-create-custom-components.md#entry)修饰的自定义组件EntryOptions命名: 472 473```ts 474// library/src/main/ets/pages/Index.ets 475// library为新建共享包自定义的名字 476@Entry({ routeName: 'myPage' }) 477@Component 478export struct MyComponent { 479 build() { 480 Row() { 481 Column() { 482 Text('Library Page') 483 .fontSize(50) 484 .fontWeight(FontWeight.Bold) 485 } 486 .width('100%') 487 } 488 .height('100%') 489 } 490} 491``` 492 493配置成功后需要在跳转的页面中引入命名路由的页面: 494 495>**说明:** 496> 497>使用命名路由方式跳转时,需要在当前应用包的oh-package.json5文件中配置依赖。例如: 498> 499>```ts 500>"dependencies": { 501> "@ohos/library": "file:../library", 502> // ... 503> } 504>``` 505 506```ts 507import { BusinessError } from '@kit.BasicServicesKit'; 508import '@ohos/library/src/main/ets/pages/Index'; // 引入共享包中的命名路由页面 509 510@Entry 511@Component 512struct Index { 513 build() { 514 Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { 515 Text('Hello World') 516 .fontSize(50) 517 .fontWeight(FontWeight.Bold) 518 .margin({ top: 20 }) 519 .backgroundColor('#ccc') 520 .onClick(() => { // 点击跳转到其他共享包中的页面 521 try { 522 this.getUIContext().getRouter().pushNamedRoute({ 523 name: 'myPage', 524 params: { 525 data1: 'message', 526 data2: { 527 data3: [123, 456, 789] 528 } 529 } 530 }) 531 } catch (err) { 532 let message = (err as BusinessError).message; 533 let code = (err as BusinessError).code; 534 console.error(`pushNamedRoute failed, code is ${code}, message is ${message}`); 535 } 536 }) 537 } 538 .width('100%') 539 .height('100%') 540 } 541} 542``` 543 544## 相关实例 545 546针对页面路由开发,有以下相关实例可供参考: 547 548- [页面布局和连接(ArkTS)(API9)](https://gitcode.com/openharmony/applications_app_samples/tree/master/code/UI/ArkTsComponentCollection/DefiningPageLayoutAndConnection) 549