发布时间:2026/7/7 2:01:01
【HarmonyOS学习笔记】2026-07-06 | 页面与数据5实现页面跳转date: 2026-07-06tags: [HarmonyOS, Navigation, 路由, 生命周期, Builder, Extend, 安全区域, ArkTS严格模式]1️⃣ 学习内容概览本节学习的是开发者学堂中构建更加丰富页面章节的页面与数据5 - 实现页面跳转围绕登录页面的构建涉及组件装饰器、Navigation 路由容器、页面跳转机制、生命周期管理、ArkTS 严格模式等核心知识点。学习过程中遇到了多个实操问题逐一排查解决。2️⃣ 核心知识点与实操问题一、装饰器体系Extend / Builder / Component / EntryExtend(组件名)— 给指定组件扩展自定义属性方法Extend(TextInput) function inputStyle() { .placeholderColor(#99182431) .height(45vp) .fontSize(18fp) .backgroundColor(#F1F3F5) .width(328vp) }关键特点特点说明必须指定组件类型Extend(TextInput)只能用于 TextInput支持传参可以传入参数实现差异化但参数不能是函数类型全局可用定义在全局作用域可封装属性和事件既能封装样式也能封装事件与Styles对比对比项ExtendStyles指定组件✅ 必须❌ 通用支持事件✅❌ 仅通用属性支持传参✅❌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对比对比项BuilderExtendComponent粒度函数级复用 UI 片段属性级扩展组件样式组件级独立组件能否有状态❌❌✅State能否传参✅✅Prop接收使用方式this.xxx().xxx()链式Xxx /ComponentstructEntry组合Entry Component struct LoginPage { build() { ... } }装饰器/关键字作用struct值类型结构体ArkTS 组件的基本单元承载状态和 UIComponent将 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 布局问题排查实操问题1Column 没有占满 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 为准。问题3Blank() 不生效原因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 // 系统规定字段指向导航路由表 } }pages和routerMap是系统规定字段名不可改名$profile:xxx指向resources/base/profile/xxx.json2.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() → 渲染 MainPageNavDestination 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.jsonroute_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()返回定时器 IDnumber 类型是定时器的唯一标识保存 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️⃣ 为什么这些问题会出现刨根问底 我的理解Navigation 的隐形占位Navigation 默认有标题栏和安全区域避让这些是隐形的——不设背景色你看不到但它们确实在占空间。这是框架的默认保护行为避免内容被状态栏遮挡。声明式 UI 的上下文限制Button()、Text()等不是普通函数而是声明式 UI 语法。它们只能在build()和Builder内使用。在普通函数或事件回调中写 UI 组件会报not callable错误。两种路由体系的混用陷阱Entry页面是独立路由页面NavDestination是 Navigation 内的子页面。两者不能混用——NavDestination 不能被loadContent直接加载Entry 页面不需要在 route_map 中注册。ArkTS 严格模式的设计哲学强制异常处理、禁止any、禁止as断言——所有可能出错的地方开发者必须显式处理。这是为了提升应用稳定性避免运行时崩溃。4️⃣ 实操验证记录问题现象排查过程解决Column 不占满棕色背景只占 60%先怀疑 height 问题后确认是标题栏占空间hideTitleBar(true)layoutWeight(1)上下留白安全区域避让对比色观察边界确认是系统安全区域expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])白屏实机全白检查 EntryAbility → 发现 loadContent 指向非 Entry 页面改为pages/LoginPageButton not callable编译报错发现错误 import 覆盖了全局 Button删除错误 importBuilder 空函数编译报错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