十二、路由、生命周期函数 原创
router路由
页面路由指的是在应用程序中实现不同页面之间的跳转,以及数据传递。通过 Router 模块就可以实现这个功
能
2.1创建页面
之前是创建的文件,使用路由的时候需要创建页面,步骤略有不同
- 方法 1:直接右键新建Page(常用)
- 方法 2:单独添加页面并配置
2.1.1直接右键新建Page
2.1.2单独添加页面并配置
1.新建页面
pages/DetailPage.ets
@Entry
@Component
struct DetailPage {
build() {
Column({ space: 15 }) {
Text('Detail Page')
.fontSize(40)
Button('Back')
}
.height('100%')
.width('100%')
.justifyContent(FlexAlign.Center)
}
}
2.调整配置
:::success 小技巧:
- 按两次 shift
:::
{
//这是一个数组,页面路径管理中心
"src": [
"pages/Index",
+ "pages/DetailPage"
]
}
:::success TIP
- 手动新建一个页面(ets)文件,需要在**main_pages.json**中手动配置
- 可以自动创建(会自动添加配置)
- 删除页面**不会**自动删除配置,需要手动删除
:::
2.2.页面跳转
接下来学习路由的跳转,页面跳转是开发过程中的一个重要组成部分。
在使用应用程序时,通常需要在不同的页面之间跳转,有时还需要将数据从一个页面传递到另一个页面。接下来咱们分场景来讲解这部分的内容:
- 页面跳转与后退
- 路由模式
- 参数传递
2.2.1页面跳转与后退
首先来看看看使用频率最高的 跳转和 后退,核心就是使用 router 的方法
// 1. 导入
import router from '@ohos.router';
// 2.调用方法-普通跳转(可以返回)
router.pushUrl({
url:'页面地址'
})
// 2.调用方法-替换跳转(无法返回)
router.replaceUrl({
url:'页面地址'
})
// 2.调用方法-返回()
router.back()
:::info 试一试:
- 创建目录,管理页面,
- 在目录下添加:首页,详情页
- 页面 A 中分别使用pushUrl和replaceUrl跳转到页面 B
- 页面 B 测试 back
:::
import router from '@ohos.router'
@Entry
@Component
struct Index {
build() {
Column({ space: 15 }) {
Text('首页')
.fontSize(40)
// 通过 router 模块进行跳转
Button('去详情页-pushUrl')
.onClick(() => {
router.pushUrl({
url: 'pages/day11/knowledges/router01/DetailPage',
})
})
Button('去详情页-replaceUrl')
.onClick(() => {
router.replaceUrl({
url: 'pages/day11/knowledges/router01/DetailPage',
})
})
}
.height('100%')
.width('100%')
.justifyContent(FlexAlign.Center)
}
}
import router from '@ohos.router'
@Entry
@Component
struct DetailPage {
build() {
Row() {
Column() {
Text('详情页')
.fontSize(50)
.fontWeight(FontWeight.Bold)
Button('返回')
.onClick(() => {
router.back()
})
}
.width('100%')
}
.height('100%')
}
}
:::info router.pushUrl() 和 router.replaceUrl()。都可以跳转页面,区别为是否会替换当前页。
- router.pushUrl():目标页面不会替换当前页,而是压入页面栈。这样可以保留当前页的状态,并且可以通过返回键或者调用router.back()方法返回到当前页。
- router.replaceUrl():目标页面会替换当前页,并销毁当前页。这样可以释放当前页的资源,并且无法返回到当前页。
划重点:pushUrl 可以返回 replaceUrl 无法返回
:::
2.2.2页面栈
页面栈是用来存储程序运行时页面的一种数据结构,遵循先进后出的原则,咱们结合刚刚的代码来说明一下:
:::info 页面栈的最大容量为32个页面
:::
2.2.2.1pushUrl的情况
先来看看 pushUrl的情况
1默认打开首页 → 首页入栈
2pushUrl 去详情页 → 详情页入栈
3back 返回上一页 → 详情页出栈
4此时页面栈中应该只有一个页面
整一个过程中,都可以 router.getLength 进行查看
2.2.2.2replaceUrl 的情况
再来看看replaceUrl的情况
1默认打开首页 → 首页入栈
2replaceUrl 去详情页 → 详情页替换首页,首页销毁
3back 无法返回 → 没有上一页
2.2.2.3页面栈相关 api
为了让咱们更好的获取页面栈的信息,router 模块也提供了对应的 api 以供使用
// 获取页面栈长度
router.getLength()
// 获取页面状态
let page = router.getState();
console.log('current index = ' + page.index);
console.log('current name = ' + page.name);
console.log('current path = ' + page.path);
// 清空页面栈
router.clear()
2.3路由模式
路由提供了两种不同的跳转模式,不同模式的决定了页面是否会创建多个实例
●Standard:多实例模式,也是默认情况下的跳转模式。目标页面会被添加到页面栈顶,无论栈中是否存在相同url的页面。
●Single:单实例模式。如果目标页面的url已经存在于页面栈中,则会将离栈顶最近的同url页面移动到栈顶,该页面成为新建页。如果目标页面的url在页面栈中不存在同url页面,则按照默认的多实例模式进行跳转。
:::info
简而言之:
1Standard:无论之前是否添加过,一直添加到页面栈【常用】
2Single:如果之前加过页面,会使用之前添加的页面【看情况】
:::
// 多实例模式下,router.RouterMode.Standard参数可以省略。
// pushUrl 和 replaceUrl 均可以在第二个参数设置 【路由模式】
router.pushUrl(options,mode)
router.replaceUrl(options,mode)
参数名 | 类型 | 必填 | 说明 |
options | 是 | 跳转页面描述信息。 | |
mode | 是 | 跳转页面使用的模式。 |
RouterMode参数说明
名称 | 说明 |
Standard | 多实例模式,也是默认情况下的跳转模式。 目标页面会被添加到页面栈顶,无论栈中是否存在相同url的页面。 说明:不使用路由跳转模式时,则按照默认的多实例模式进行跳转。 |
Single | 单实例模式。 如果目标页面的url已经存在于页面栈中,则该url页面移动到栈顶。 如果目标页面的url在页面栈中不存在同url页面,则按照默认的多实例模式进行跳转。 |
:::info
试一试:
1创建目录管理多个页面:
a01.HomePage
b02.MoviePage
c03.ActorPage
2整合基础模版
3分别测试两种路由模式:
:::
@Entry
@Component
struct HomePage {
build() {
Stack() {
Image($r("app.media.douban_home"))
.width('100%')
Button('电影详情')
.translate({ x: -120 })
.onClick(() => {
})
}
.height('100%')
}
}
@Entry
@Component
struct MoviePage {
@State message: string = 'Hello World';
build() {
Stack() {
Image($r("app.media.douban_movie"))
.width('100%')
Button('演员')
.translate({ x: -35, y: 180 })
.onClick(() => {
})
}
.height('100%')
}
}
@Entry
@Component
struct ActorPage {
build() {
Stack() {
Image($r("app.media.douban_actor"))
Button('电影')
.translate({ x: 43, y: 80 })
.onClick(() => {
})
}
.height('100%')
}
}
2.3.1Strandard模式
使用 Strandard 模式,在电影和演员页面反复跳转时会持续往页面栈中添加新的页面,浪费内存
2.3.2****Single模式
使用 Single 模式,在页面和演员页面反复跳转时会将已有的页面移到栈顶,避免浪费内存
2.4参数
接下来看看另外一个挺常见的需求:传递参数
日常开发中有这样的场景:点击不同的电影,商品,标题。。。跳转到与之对应的详情页面。
详情页面的布局是类似的,但是内容被替换为与之对应的内容。这就是一个常见的需要传递参数的场景:
列表 → 详情
2.4.1参数传递及接收
首先来看看如何实现页面参数传递和获取
// -----------传递参数------------
// 普通跳转 并 传递参数
router.pushUrl({
url:'地址',
params:{
// 以对象的形式传递参数
}
})
// 覆盖跳转并传递参数
router.replaceUrl(
url:'地址',
params:{
// 以对象的形式传递参数
}
)
// 返回并传递参数
router.back(
url:'地址',
params:{
// 以对象的形式传递参数
}
)
// -------------页面 B接收并解析参数------------
// aboutToAppear一会展开 (生命周期函数)
aboutToAppear(): void {
// 1.确认内容
console.log(JSON.stringify(router.getParams()))
// 2.通过 as 类型断言 转为具体的类型
const params = router.getParams() as 类型
// 3. 通过点语法即可取值
params.xxx
}
options参数说明
名称 | 类型 | 必填 | 说明 |
url | string | 是 | 表示目标页面的url,可以用以下两种格式: - 页面绝对路径,由配置文件中pages列表提供,例如: - pages/index/index - pages/detail/detail - 特殊值,如果url的值是"/",则跳转到首页。 |
params | object | 否 | 表示路由跳转时要同时传递到目标页面的数据,切换到其他页面时,当前接收的数据失效。跳转到目标页面后,使用router.getParams()获取传递的参数 |
试一试:
1创建目录管理页面
a创建首页
b创建详情页
2【首页】携带数据去 【详情页】
3【详情页】接收并解析数据
:::
export class MovieInfo {
id: number = 0
}
@Entry
@Component
struct Home {
build() {
Stack() {
Image($r('app.media.douban_home'))
.width('100%')
Row({ space: 10 }) {
Button('热辣滚烫')
.onClick(() => {
// id: 36081094 图片 params_movie1
})
Button('第二十条')
.onClick(() => {
// id: 36208094 图片 params_movie2
})
Button('飞驰人生')
.onClick(() => {
// id: 36369452 图片 params_movie3
})
}
.translate({ x: -10 })
}
.height('100%')
}
}
@Entry
@Component
struct Params {
@State imgUrl: ResourceStr = $r('app.media.params_movie1');
aboutToAppear(): void {
// 根据 传递过来的 id 决定渲染不同的图片
// 热辣滚烫 // id == 36081094
// 第二十条 // id == 36208094
// 飞驰人生 2 // id == 36369452
}
build() {
Row() {
Image(this.imgUrl)
}
.height('100%')
}
}
router路由案例-共享元素转场
当路由进行切换时,可以通过设置组件的 sharedTransition 属性将该元素标记为共享元素并设置对应的共享元素转场动效。
链接
// 页面 A
组件(){
}
.sharedTransition('标记', { duration: 500 })
// 页面 B
组件(){
}
.sharedTransition('标记', { duration: 500 })
动画属性
名称 | 参数类型 | 是否必填 | 参数描述 |
duration | number | 否 | 描述共享元素转场动效播放时长。 默认值:1000。 单位:毫秒。 |
curve | Curve | string | ICurve 10+ |
delay | number | 否 | 延迟播放时间。 默认值:0。 单位:毫秒。 |
motionPath | 否 | 运动路径信息。 | |
zIndex | number | 否 | 设置Z轴。 |
type | 否 | 动画类型。 默认值:SharedTransitionEffectType.Exchange。 |
:::info
试一试:
1基础基础模版实现,图片和文字的转场效果
:::
import router from '@ohos.router'
@Entry
@Component
struct Index {
build() {
Row({ space: 15 }) {
Column({ space: 10 }) {
Image('https://qna.smzdm.com/202404/01/660a60fac2f4c6567.jpg_e680.jpg')
.width('100%')
.aspectRatio(1)
Text('小米创世版第二轮开售,一分钟秒没,我劝大家荔枝一点')
}
.padding(15)
.width('50%')
.onClick(() => {
})
}
.width('100%')
}
}
@Entry
@Component
struct DetailPage {
build() {
Column({ space: 15 }) {
Column({ space: 10 }) {
Image('https://qna.smzdm.com/202404/01/660a60fac2f4c6567.jpg_e680.jpg')
.width('100%')
.aspectRatio(1)
Text('小米创世版第二轮开售,一分钟秒没,我劝大家荔枝一点')
.fontSize(18)
}
.padding(15)
.onClick(() => {
})
}
.height('100%')
.width('100%')
}
}
页面和自定义组件的生命周期
组件和页面在创建、显示、销毁的这一整个过程中,会自动执行 一系列的【生命周期钩子】,其实就是一系列的【函数】,让开发者有机会在特定的阶段运行自己的代码
在开始之前,我们先明确自定义组件和页面的关系:
●自定义组件:@Component 装饰的UI单元,
●页面:即应用的UI页面。可以由一个或者多个自定义组件组成。@Entry装饰的自定义组件为页面的入口组件,即页面的根节点,一个页面有且仅能有一个@Entry。
4.1组件-生命周期
仅仅 @Component 组件
aboutToAppear
●aboutToAppear 函数在创建自定义组件的新实例后,在执行其 build 函数之前执行。
●允许在 aboutToAppear 函数中改变状态变量,更改将在后续执行 build 函数中生效。
aboutToDisappear
●aboutToDisappear 函数在自定义组件析构销毁之前执行。
●不允许在 aboutToDisappear 函数中改变状态变量,特别是 @Link 变量的修改可能会导致应用程序行为不稳定。
@Component
struct SonCom {
aboutToAppear(): void {
console.log('组件创建')
}
aboutToDisappear(): void {
console.log('组件销毁')
}
build() {
Column() {
Text('我是子组件')
}
.width(200)
.height(200)
.border({ width: .5 })
}
}
@Entry
@Component
struct LiteFunc01 {
@State show: boolean = false
build() {
Column() {
Text('组件的生命周期')
Button('切换子组件显示')
.onClick(() => {
this.show = !this.show
})
if (this.show) {
SonCom()
}
}
}
}
4.2页面-生命周期
仅页面 @Entry 组件
onPageShow
●页面每次显示时触发一次,包括路由过程、应用进入前后台等场景,仅 @Entry 修饰的自定义组件生效。
onPageHide
●页面每次隐藏时触发一次,包括路由过程、应用进入前后台等场景,仅 @Entry 修饰的自定义组件生效。
onBackPress
●当用户点击返回按钮时触发,仅 @Entry 修饰的自定义组件生效。
●内部如果返回 true,就无法通过返回键返回上一页,用户就必须和页面交互才可以返回
:::info 因为@Entry 也是@Component组件,所以页面组件同时拥有自定义组件的生命周期
:::
import router from '@ohos.router';
import promptAction from '@ohos.promptAction';
import { User } from './01.Router01';
import { BusinessError } from '@ohos.base';
@Entry
@Component
struct DetailPage {
// aboutToAppear 会在 build 执行之前调用,后续章节展开进行讲解
aboutToAppear() {
const params = router.getParams() as User
if (params) {
promptAction.showToast({ message: params.name + '|' + params.age })
console.log("页面初始化")
}
}
onPageShow() {
console.log("页面显示")
}
onPageHide() {
console.log("页面隐藏")
}
onBackPress() {
console.log("按了后退键")
// 阻止点击返回的操作,
return true
}
build() {
Column({ space: 15 }) {
Text('Detail Page')
.fontSize(40)
Button('Back')
.onClick(() => {
router.back()
})
}
.height('100%')
.width('100%')
.justifyContent(FlexAlign.Center)
}
}
Navigation路由导航容器组件(推荐)
认识Navigation组件
Navigation相比于普通的容器组件,Navigation可以实现路由跳转。一般作为首页的根容器,包括单栏、分栏和自适应三种显示模式。适用于一次开发,多端部署场景。
/pɑːθ/
Navigation的路由跳转操作都是基于页面栈NavPathStack实例提供的方法进行,每个Navigation都需要创建并传入一个NavPathStack对象,用于管理页面。主要涉及页面跳转、页面返回、路由拦截等功能。
Navigation进行路由跳转的写法
具体的使用步骤是:
- 在根页面写法:
@Entry
@Component
struct Index {
@State message: string = '第一个页面';
//1. 创建一个页面栈对象并传入Navigation
pageStack: NavPathStack = new NavPathStack()
build() {
//2. 绑定页面栈对象
Navigation(this.pageStack) {
Text(this.message)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.onClick(() => {
//3.点击事件里写跳转逻辑
this.pageStack.pushPath({ name: "minePage" })
})
}
.onAppear(() => {
this.registerInterceptors()
})
.height('100%')
.width('100%')
.title('首页') //标题
.titleMode(NavigationTitleMode.Full) //标题样式
.hideTitleBar(false) //标题是否隐藏
}
}
- 在子页面写法:
@Builder
function mineBuilder() {
MineComp()
}
@Component
struct MineComp {
build() {
NavDestination() {
Column() {
Text('我的')
}
}.title('我的页面')
}
}
- 路由配置:
//路由配置在:src/main/resources/base/profile/route_map.json
{
"routerMap": [
{
"name": "minePage", //子页面跳转的名字
"pageSourceFile": "src/main/ets/pages/Navigation/mine.ets",//路由跳转的路径
"buildFunction": "mineBuilder"//子页面的@Builder构建函数
}
]
}
- module.json5中配置依赖
路由拦截
路由拦截在NavPathStack实例里面的willShow页面跳转前的回调里进行阻止判断的逻辑, 和使用pop()API , 进行阻断。
//注册拦截器
registerInterceptors() {
//设置Navigation页面跳转回调
this.pageStack.setInterception({
//页面跳转前的回调
willShow: (from, to) => {
//from从哪里来(信息)
//to到哪里去
//判断要去的页面是否是Navigation首页 == navbar(首页的标识)
if (typeof to == 'string') {
return
}
if (to.pathInfo.name == 'mine') {
promptAction.showToast({
message: JSON.stringify(to.pathInfo)
})
// 根据Token的值来判断用户是否登录
// 1.有Token可以跳转成功
// 2.没有Token不可以跳转
const token = AppStorage.get<string>('token')
if (!token) {
//不让你跳, 拦截处理
to.pathStack.pop()
//跳转到指定页面, 如登录页面
to.pathStack.pushPath({
name: 'Login'
})
console.log('1111')
}
// //不让你跳, 拦截处理(回到上一页 - 回到栈顶)
// to.pathStack.pop()
}
}
})
}
子页面继续路由跳转的两种方法
1. 使用@provide、@consume状态变量实现跨层传递NavPathStack实例
2. NavDestination 注册onReady事件:
获取上下文对象。进而获取根页面路由栈实例(所有组件页面共享同一个实例)
还有一些属性
- title设置标题,
- memus设置右上角菜单,
- mode可以设置分栏