一、概念
页面路由是指在应用程序中实现不同页面之间的跳转和数据传递。
案例:第一次使用某个购物应用,打开时肯定会是一个登录页,在登录成功以后,会跳转到首页,然后可能会去搜索,就会进入到搜索列表页,接着呢如果搜索到某一个感兴趣的商品A,点击,就会进入到商品A的详情页,到现在为止已经访问了好多不同的页面,并且在它们之间完成了跳转,那我之前访问过的页面都去哪里了?是不是全部被销毁了?其实并没有,我们在页面跳转之间访问过的所有页面,都会被 HarmonyOS 保存到
页面栈
的空间当中。页面栈
顾名思义就是保存页面的栈结构空间,栈结构是先进后出,所以呢,我们最早访问的登录页就被压到了栈的最底层,而当前正在访问的商品A的详情页就在栈顶。也就是说,谁在栈顶,当前显示的就是谁的页面。那么 HarmonyOS 为啥要把这些访问过的页面保存起来,而不是直接销毁掉呢?放在这里不占用内存吗?这其实是我们的页面功能需要去用到这些历史页面。一般商品详情页都会有返回按钮,当点击返回按钮,应该返回到之前访问过的搜索列表页,有了页面栈,想要实现这个功能就非常简单了。只需要在点击返回时,把栈顶的这个页面移除,这样一来,紧挨着栈顶的搜索列表页就成为了新的栈顶页面,现在显示的就是搜索列表页,从而也就实现了返回的效果。如果现在想做页面跳转,过程就相反,比如在搜索列表页点击商品B,只需要把商品B的详情页创建出来,然后压入栈里,现在栈顶就是商品B的详情页,从而实现了跳转效果。简单来讲,如果想实现创建页面,就压入栈;如果想实现返回,就把栈顶页面弹出栈,即可。
-
页面栈的最大容量:
页面栈的最大容量上限为
32
个页面。就是说如果我们不断的去访问新的页面,往栈里压入页面,可能就会达到上限,这时候再想访问这个页面,再想往里面去压栈,就会报错,这时候就不得不去调用router.clear()
方法去清空页面栈,就会把历史页面干掉,释放内存。但是,一旦把历史页面干掉,再想返回前一个页面就访问不了了。所以router.clear()
要慎重使用。我们在开发的过程中一定要想办法控制页面栈里的页面数量,不要让它达到上限,而不是说等达到上限去清空。 -
怎么去控制页面栈里的页面数量呢?就要使用页面栈不同的跳转行为模式。Router 有两种页面跳转模式:
-
router.pushUrl()
:目标页不会替换当前页,而是压入页面栈。比如当前在商品A的详情页,如果点击商品B的图片,就需要压入栈,就需要创建一个商品B的页面,把商品B的详情页压入栈顶,这时候,原有的商品A的详情页不会被移除,而是压到栈的内部,成为一个历史页面,因此点返回按钮,用router.back()
就会返回到历史页面商品A的详情页。但是,这种实现方式会导致栈里的页面会越来越多。 -
router.replaceUrl()
:目标页替换当前页,当前页会被销毁并释放资源。也就是说从商品A的详情页跳转到商品B的详情页,这时候商品A的详情页就变成了历史页,会直接被销毁,而不是在栈内保存。这样一来,内存就节省出来,但是如果想从商品B的详情页返回到商品A的详情页,就返回不了了。
-
-
何时使用
router.pushUrl()
,何时又使用router.replaceUrl()
呢?举个例子,比如说,我们的登录页,只有在第一次打开的时候才需要,只要不退出,就不用再登录。所以登录页基本上就访问一次,而且也不需要返回,登录页保存在历史页面栈里没有任何意义,所以在登录成功以后,跳转到首页时,就可以使用
router.replaceUrl()
把登录页销毁;如果我们从首页跳转到搜索列表页,如果这时候点返回,返回到首页,所以我们的首页应该在页面栈里保存,作为一个历史页面,就要用router.pushUrl()
。 -
如果商品A的详情页和商品B的详情页来回切换,如果用到
router.pushUrl()
,会导致页面栈的容量一会儿就满了,就要用到页面实例模式,Router 有两种页面实例模式:-
Standard
:标准实例模式,每次跳转都会新建一个目标页并压入栈顶。默认就是这种模式。 -
Single
:单实例模式,顾名思义,每一个页面只会存在一份,如果目标页已经在栈中,则离栈顶最近的同Url页面会被移动到栈顶并重新加载。
-
结合合适的跳转模式(
router.pushUrl()
、router.replaceUrl()
)和实例模式(Standard
、Single
),就能够控制页面栈里的页面数量,避免达到上限。
二、Router API 用法
-
首先要导入 HarmonyOS 提供的 Router 模块:
tsimport router from '@ohos.router';
-
然后利用 router 实现跳转、返回等操作:
tsrouter.pushUrl( { url: 'pages/PageA', params: { id: 1 } }, router.RouterMode.Single, err => { if (err) { console.log('路由失败。') } } )
-
RouterOptions
- url:目标页面路径
- params:传递的参数(可选)
-
RouterMode
- Standard:标准实例模式
- Single:单实例模式
-
异常响应回调函数
- 错误码 100001:内部错误,可能是渲染失败
- 错误码 100002:路由地址错误
- 错误码 100003:路由栈中页面超过32
-
-
目标页获取传递过来的参数
tsparams: any = router.getParams()
-
目标页返回上一页
tsrouter.back()
-
目标页返回指定页,并携带参数
tsrouter.back({ url: 'pages/Index', params: { id: 10 } })
三、示例
-
代码目录结构
|____src | |____main | | |____resources | | | | |____profile | | | | | |____main_pages.json | | | | |____media | | | | | |____back.png | | |____ets | | | |____components | | | | |____CommonComponents.ets | | | |____pages | | | | |____PageD.ets | | | | |____PageC.ets | | | | |____PageB.ets | | | | |____PageA.ets | | | | |____Index.ets
-
main_pages.json
json{ "src": [ "pages/Index", "pages/PageA", "pages/PageB", "pages/PageC", "pages/PageD" ] }
-
CommonComponents.ets
tsimport router from '@ohos.router' @Component export struct Header { @State params: any = router.getParams() build() { Row({ space: 5 }) { Image($r('app.media.back')) .width(30) .onClick(() => { // 返回前的警告 router.showAlertBeforeBackPage({ message: 'Show Alert Before Back Page' }) // 返回上一页 router.back() }) if (this.params) { Text(`Params id: ${this.params.id}`) .fontSize(28) .fontWeight(FontWeight.Bold) } } .width('98%') .height(30) } }
-
Index.ets
tsimport router from '@ohos.router' class RouterInfo { // 页面路径 url: string // 页面标题 title: string constructor(url: string, title: string) { this.url = url this.title = title } } @Entry @Component struct Index { @State message: string = '页面列表' private routers: RouterInfo[] = [ new RouterInfo('pages/PageA', 'A页面'), new RouterInfo('pages/PageB', 'B页面'), new RouterInfo('pages/PageC', 'C页面'), new RouterInfo('pages/PageD', 'D页面') ] build() { Column() { Text(this.message) .fontSize(50) .fontWeight(FontWeight.Bold) .height(80) List({ space: 15 }) { ForEach( this.routers, (router, index) => { ListItem() { this.RouterItem(router, index + 1) } } ) } .layoutWeight(1) .alignListItem(ListItemAlign.Center) .width('100%') } .width('100%') .height('100%') } @Builder RouterItem(r: RouterInfo, i: number) { Row() { Text(i + '. ') .fontSize(20) .fontColor(Color.White) Text(r.title) .fontSize(20) .fontColor(Color.White) } .width('90%') .padding(12) .backgroundColor('#38F') .borderRadius(20) .shadow({ radius: 6, color: '#4F000000', offsetX: 2, offsetY: 2 }) .onClick(() => { // router 跳转 router.pushUrl( { url: r.url, params: { id: i } }, router.RouterMode.Single, err => { if (err) { console.log(`路由失败,errCode: ${err.code} errMsg: ${err.message}`) } } ) }) } }
-
PageA.ets
tsimport { Header } from '../components/CommonComponents' @Entry @Component struct PageA { @State message: string = 'Page A' build() { Column() { Header() Row() { Column() { Text(this.message) .fontSize(50) .fontWeight(FontWeight.Bold) } .width('100%') } .height('100%') }.width('100%') } }
-
运行效果
四、总结
-
页面栈的最大容量上限为
32
个页面,使用router.clear()
方法可以清空页面栈,释放内存。 -
Router 有两种页面跳转模式,分别是:
router.pushUrl()
:目标页不会 替换当前页,而是压入页面栈,因此可以用router.back()
返回当前页。router.replaceUrl()
:目标页会替换当前页,当前页会被销毁并释放资源,无法返回当前页。
-
Router 有两种页面实例模式,分别是:
Standard
:标准实例模式,每次调整都会新建一个目标并压入栈顶。默认就是这种模式。Single
:单实例模式,如果目标页已经在栈中,则离栈顶最近的同 url 页面会被移动到栈顶并重新加载。
-
router 的使用步骤:
- 导入 router 模块
- 使用 router 的 API