【HarmonyOS学习笔记】2026-07-06 | 页面与数据5:实现页面跳转

【HarmonyOS学习笔记】2026-07-06 | 页面与数据5:实现页面跳转

【HarmonyOS学习笔记】2026-07-06 | 页面与数据5:实现页面跳转


date: 2026-07-06
tags: [HarmonyOS, Navigation, 路由, 生命周期, @Builder, @Extend, 安全区域, ArkTS严格模式]

1️⃣ 学习内容概览

本节学习的是"开发者学堂"中"构建更加丰富页面"章节的"页面与数据5 - 实现页面跳转",围绕登录页面的构建,涉及组件装饰器、Navigation 路由容器、页面跳转机制、生命周期管理、ArkTS 严格模式等核心知识点。学习过程中遇到了多个实操问题,逐一排查解决。


2️⃣ 核心知识点与实操问题

一、装饰器体系:@Extend / @Builder / @Component / @Entry

@Extend(组件名)— 给指定组件扩展自定义属性方法
@Extend(TextInput) function inputStyle() { .placeholderColor('#99182431') .height('45vp') .fontSize('18fp') .backgroundColor('#F1F3F5') .width('328vp') }

关键特点:

特点说明
必须指定组件类型@Extend(TextInput)只能用于 TextInput
支持传参可以传入参数实现差异化(但参数不能是函数类型)
全局可用定义在全局作用域
可封装属性和事件既能封装样式,也能封装事件

@Styles对比:

对比项@Extend@Styles
指定组件✅ 必须❌ 通用
支持事件❌ 仅通用属性
支持传参
@Builder— 声明 UI 构建函数
@Builder imageButton(src: Resource) { Button({ type: ButtonType.Circle, stateEffect: true }) { Image(src) } .height('48vp') .width('48vp') .backgroundColor('#F1F3F5') }

⚠️ 关键规则:@Builder必须逐个添加,不会自动延伸到下一个函数!

@Builder imageButton(src: Resource) { ... } // ✅ 有 @Builder @Builder // ← 必须再加一个! login(isLogin: boolean) { ... } // 如果不加,login 就是普通函数

⚠️@Builder函数不能为空,必须有至少一个 UI 组件作为返回值。

调用方式:组件内用this.xxx()调用。

@Extend@Component对比:

对比项@Builder@Extend@Component
粒度函数级,复用 UI 片段属性级,扩展组件样式组件级,独立组件
能否有状态@State
能否传参@Prop接收
使用方式this.xxx().xxx()链式<Xxx />
@Component+struct+@Entry组合
@Entry @Component struct LoginPage { build() { ... } }
装饰器/关键字作用
struct值类型结构体,ArkTS 组件的基本单元,承载状态和 UI
@Component将 struct 声明为 UI 组件,必须实现build()
@Entry标记为页面入口,可被路由导航到

通俗类比:

  • struct= 空白画布
  • @Component= 装上画笔和颜料(能画 UI 了)
  • @Entry= 挂到展厅墙上(变成可跳转的页面)

二、Navigation 路由容器

官方定义

Navigation 是路由容器组件,一般作为首页的根容器,支持单页面(Stack)、分栏(Split)、导航栏(Declare)三种模式,支持 NavDestination 子页面的路由跳转。

三种导航模式
模式常量适用场景
Stack(栈模式)NavigationMode.Stack手机端最常见,推入推出
Split(分栏模式)NavigationMode.Split平板端,左右分栏
Declare(声明模式)NavigationMode.Declare自定义导航栏
路由操作
操作方法
跳转navStack.pushPath({ name: 'PageName' })pushPathByName('PageName', null)
返回navStack.pop()
替换navStack.replacePath({ name: 'PageName' })
清空navStack.clear()
Navigation vs router
对比项Navigationrouter
架构单页面内导航(组件级)跨页面导航(页面级)
状态共享同一 Navigation 下可共享页面间需传参
转场动画更丰富的内置支持基础转场
推荐程度✅ 官方推荐新项目旧项目兼容

三、Navigation 布局问题排查(实操)

问题1:Column 没有占满 Navigation

原因:Navigation 默认有标题栏(即使没设.title()),占据空间。

解决:.hideTitleBar(true)+.layoutWeight(1)组合使用。

操作效果
hideTitleBar(true)消除标题栏,内容区域变大
layoutWeight(1)Column 撑满内容区域
两者结合✅ Column 撑满全屏

⚠️ 单独用layoutWeight(1)不行:内容区域本身不够大(被标题栏压缩了),Column 占满的只是被压缩后的区域。

⚠️height('100%')vslayoutWeight(1)在 Navigation 内部,height('100%')计算可能不精确,推荐用layoutWeight(1)替代。

问题2:上下仍有留白(安全区域)

原因:Navigation 默认避让系统安全区域(状态栏 + 底部导航条)。

解决:

.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])

⚠️ API 纠正:是SafeAreaEdge,不是SafeAreaSide实操验证比文档更可靠,遇到 API 差异以实际 SDK 为准。

问题3:Blank() 不生效

原因:Blank()需要父容器有明确高度才能工作,配合layoutWeight(1)height('100%')使用。


四、白屏问题排查(实机测试)

根因:EntryAbility.ets加载了错误的页面。

// ❌ 错误:MainPage 没有 @Entry,且是 NavDestination windowStage.loadContent('pages/MainPage', (err) => { ... }); // ✅ 正确:加载 @Entry 入口页面 windowStage.loadContent('pages/LoginPage', (err) => { ... });

NavDestination 子页面不能作为入口页面直接加载,必须通过 Navigation 容器的路由跳转到达。


五、路由联动机制(重点)

三个配置文件的职责

1.module.json5— 桥梁配置

{ "module": { "pages": "$profile:main_pages", // 系统规定字段,指向入口页面清单 "routerMap": "$profile:route_map" // 系统规定字段,指向导航路由表 } }
  • pagesrouterMap系统规定字段名,不可改名
  • $profile:xxx指向resources/base/profile/xxx.json

2.main_pages.json— 入口页面清单

{"src":["pages/LoginPage"]}
  • 只放@Entry标记的页面
  • 路径格式:pages/xxx,不带.ets后缀

3.route_map.json— 导航路由表

{"routerMap":[{"name":"MainPage","pageSourceFile":"src/main/ets/pages/MainPage.ets","buildFunction":"MainPageBuilder","data":{"description":"this is mainPage"}}]}
字段含义是否自定义
name路由名称,pushPathByName()的参数✅ 可自定义,需与代码一致
pageSourceFile页面源文件路径✅ 但有格式要求
buildFunction构建函数名✅ 需与代码中导出的@Builder函数名一致
data自定义附加数据✅ 可选
完整联动流程
1. 应用启动 → 系统读取 module.json5 ├── pages → main_pages.json → 入口 "pages/LoginPage" └── routerMap → route_map.json → 注册 "MainPage" 路由 2. EntryAbility.loadContent('pages/LoginPage') → LoginPage 渲染(@Entry 页面,含 Navigation 容器) 3. 用户点击登录 → pathStack.pushPathByName('MainPage', null) 4. 系统查找 route_map.json → name: "MainPage" → buildFunction: "MainPageBuilder" 5. 调用 MainPageBuilder() → 渲染 MainPage(NavDestination) 6. MainPage 显示在 Navigation 容器内 ✅
对应代码结构
// MainPage.ets 中必须有: @Builder export function MainPageBuilder() { // ← 与 route_map 的 buildFunction 一致 MainPage() } // @Entry ← 注意:没有 @Entry! @Component export struct MainPage { build() { NavDestination() { ... } // ← 必须是 NavDestination 包裹 } }
两种路由体系对比
router(传统)Navigation + NavPathStack(推荐)
页面标记全部@Entry入口@Entry,子页面NavDestination
跳转方式router.pushUrl()pathStack.pushPathByName()
状态共享页面间独立同一 Navigation 下可共享
路由表main_pages.jsonmain_pages.json+route_map.json

六、组件生命周期

生命周期触发时机用途
aboutToAppear()组件即将显示初始化数据、订阅事件
aboutToDisappear()组件即将销毁清理资源、取消定时器
onPageShow()页面显示到前台刷新数据
onPageHide()页面退到后台暂停操作

实操应用:清理 setTimeout 定时器

private timeOutId: number = -1; login(result: boolean): void { this.isShowProgress = true; if (this.timeOutId === -1) { this.timeOutId = setTimeout(() => { this.isShowProgress = false; this.timeOutId = -1; this.pathStack.pushPathByName('MainPage', null); }, 2000); } } aboutToDisappear() { clearTimeout(this.timeOutId); // 防止页面销毁后定时器仍在执行 this.timeOutId = -1; }

原则:在aboutToAppear中创建的资源,必须在aboutToDisappear中清理。


七、setTimeout返回值与防重复点击

  • setTimeout()返回定时器 ID(number 类型),是定时器的唯一标识
  • 保存 ID 后可用于clearTimeout(id)取消定时器
  • timeOutId === -1判断防止重复点击登录

八、ArkTS 严格模式:异常处理

Function may throw exceptions. Special handling is required.

含义:所有可能抛出异常的函数,调用时必须用try-catch包裹。

// ❌ 裸调,编译报错 promptAction.openToast({ message: '账号密码不能为空!' }) // ✅ try-catch 包裹 try { promptAction.openToast({ message: '账号密码不能为空!' }) } catch (err) { // 处理异常 }

常见触发函数:promptAction.openToast()pathStack.pushPathByName()router.pushUrl()等。

与 JS 的区别:JS 编译不检查异常处理,ArkTS 编译时强制要求


九、错误 import 导致类型冲突

This expression is not callable. Type 'typeof Button' has no call signatures.

根因:错误导入了import { Button } from '@ohos.multimodalInput.mouseEvent',这个 Button 是鼠标事件类型,不是 ArkUI 的 Button 组件。

解决:ArkUI 组件(Button/Text/Image 等)是全局内置的,不需要也不应该 import


3️⃣ 为什么这些问题会出现?(刨根问底)

🧠 我的理解

  1. Navigation 的"隐形占位":Navigation 默认有标题栏和安全区域避让,这些是"隐形的"——不设背景色你看不到,但它们确实在占空间。这是框架的默认保护行为,避免内容被状态栏遮挡。

  2. 声明式 UI 的上下文限制Button()Text()等不是普通函数,而是声明式 UI 语法。它们只能在build()@Builder内使用。在普通函数或事件回调中写 UI 组件会报"not callable"错误。

  3. 两种路由体系的混用陷阱@Entry页面是独立路由页面,NavDestination是 Navigation 内的子页面。两者不能混用——NavDestination 不能被loadContent直接加载,@Entry 页面不需要在 route_map 中注册。

  4. ArkTS 严格模式的设计哲学:强制异常处理、禁止any、禁止as断言——所有可能出错的地方,开发者必须显式处理。这是为了提升应用稳定性,避免运行时崩溃。


4️⃣ 实操验证记录

问题现象排查过程解决
Column 不占满棕色背景只占 60%先怀疑 height 问题,后确认是标题栏占空间hideTitleBar(true)+layoutWeight(1)
上下留白安全区域避让对比色观察边界,确认是系统安全区域expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
白屏实机全白检查 EntryAbility → 发现 loadContent 指向非 @Entry 页面改为pages/LoginPage
Button not callable编译报错发现错误 import 覆盖了全局 Button删除错误 import
@Builder 空函数编译报错@Builder 必须有 UI 返回值按需决定是否加 @Builder
异常未处理ArkTSCheck 报错ArkTS 严格模式要求try-catch 包裹

5️⃣ 最终结论(最佳实践)

维度建议
Navigation 布局hideTitleBar(true)+layoutWeight(1)+expandSafeArea()三件套
安全区域 APISafeAreaEdge(非 SafeAreaSide),以实际 SDK 为准
路由页面标记@Entry= 入口页面,NavDestination= 子页面,不可混用
路由配置main_pages.json放 @Entry 页面,route_map.json放 NavDestination 页面
@Builder 使用逐个添加、不能为空、只能在构建上下文中调用
组件 importArkUI 组件全局内置,禁止 import,避免类型冲突
生命周期清理aboutToDisappear中清理定时器、订阅等资源
异常处理ArkTS 强制 try-catch,编译时检查
防重复操作用标识变量(如timeOutId === -1)防止重复触发
// 登录方法最佳实践 login(result: boolean): void { if (this.account === '' || this.password === '') { try { promptAction.openToast({ message: '账号密码不能为空!' }) } catch (err) { /* 处理异常 */ } } else { this.isShowProgress = true; if (this.timeOutId === -1) { this.timeOutId = setTimeout(() => { this.isShowProgress = false; this.timeOutId = -1; try { this.pathStack.pushPathByName('MainPage', null); } catch (err) { /* 处理异常 */ } }, 2000); } } } aboutToDisappear() { clearTimeout(this.timeOutId); this.timeOutId = -1; }

核心原则:Navigation 是页面的"总管",它的标题栏、安全区域、路由栈是三个独立的"隐形层",必须显式处理才能让内容真正占满屏幕;ArkTS 严格模式要求开发者对所有潜在风险(异常、类型安全)显式负责,不存在"假装没看见"。


随笔小结:今天围绕登录页面构建,系统学习了装饰器体系(@Extend/@Builder/@Component/@Entry)、Navigation 路由容器的布局和路由机制、页面跳转的配置联动(main_pages.json + route_map.json + module.json5)、组件生命周期管理、ArkTS 严格模式的异常处理要求。实操中遇到的最大坑是 Navigation 的"隐形占位"(标题栏 + 安全区域)导致布局不预期,以及 EntryAbility 加载非 @Entry 页面导致白屏。核心收获:框架的默认保护行为(标题栏、安全避让、异常检查)都需要开发者显式覆盖,理解这些默认行为是排错的关键。

懿路向前 · AI辅助整理
2026-07-06