《鸿蒙APP开发从入门到精通》第5篇:页面路由与组件跳转 📲

内容承接与核心价值
这是《鸿蒙APP开发从入门到精通》的第5篇------架构导航篇 ,承接第4篇的「网络请求与数据持久化」,100%复用项目架构 ,为后续第6-12篇的电商购物车全栈项目 铺垫页面导航 和组件间通信的核心技术。
学习目标:
- 掌握鸿蒙官方页面路由库的用法;
- 实现首页 →商品详情 →购物车 →订单 →支付的完整页面跳转;
- 理解组件间通信的原理与实现方式(路由参数、全局状态管理);
- 优化页面跳转体验(预加载、动画、路由拦截)。
学习重点:
- 页面路由的基本用法(push、pop、replace);
- 路由参数的传递与接收(基本类型、复杂对象、回调函数);
- 全局状态管理(AppStorage、LocalStorage);
- 页面路由的高级特性(预加载、动画、拦截器)。
一、 页面路由基础 🚀
1.1 鸿蒙页面路由库
HarmonyOS Next提供了官方页面路由库,支持以下功能:
- 页面跳转(push、pop、replace、relaunch);
- 路由参数传递与接收(基本类型、复杂对象);
- 页面栈管理(获取当前页面栈、清空页面栈);
- 预加载与动画效果;
- 路由拦截器(登录验证、权限检查)。
1.2 实战:页面路由配置
1. 页面配置文件
在「entry/src/main/module.json5」中配置页面路由:
json
{
"module": {
"pages": [
"pages/Index",
"pages/SearchPage",
"pages/ProductDetailPage",
"pages/CartPage",
"pages/OrderPage",
"pages/PaymentPage",
"pages/LoginPage",
"pages/MyPage"
]
}
}
2. 路由工具类
⌨️ entry/src/main/ets/utils/RouterUtils.ets
typescript
import router from '@ohos.router';
// 页面路由参数类型
export interface RouterParams {
[key: string]: any;
}
// 页面路由工具类
export class RouterUtils {
// 跳转到指定页面
static push(url: string, params?: RouterParams): void {
router.pushUrl({ url, params });
}
// 跳转到指定页面并替换当前页面
static replace(url: string, params?: RouterParams): void {
router.replaceUrl({ url, params });
}
// 返回到上一页
static pop(): void {
router.back();
}
// 返回到指定页面
static popTo(url: string): void {
router.backTo({ url });
}
// 重新加载应用
static relaunch(): void {
router.relaunch({ url: 'pages/Index' });
}
// 获取当前页面参数
static getParams(): RouterParams {
return router.getParams() || {};
}
// 获取当前页面栈
static getPages(): Array<router.RouterState> {
return router.getPages();
}
// 清空页面栈
static clear(): void {
router.clear();
}
}
二、 页面跳转实战 🛠️
2.1 实战目标
基于第4篇的「MyFirstHarmonyApp」项目架构,实现以下功能:
- 首页→商品详情:点击商品列表跳转到商品详情页面,传递商品ID;
- 商品详情→购物车:点击加入购物车跳转到购物车页面,传递商品ID与数量;
- 购物车→订单:点击结算跳转到订单页面,传递选中商品信息;
- 订单→支付:点击支付跳转到支付页面,传递订单信息;
- 支付→订单:支付成功后返回到订单页面,传递支付结果。
2.2 🔧 页面跳转实现
1. 首页→商品详情
⌨️ entry/src/main/ets/components/GoodsListComponent.ets(修改GoodsItemComponent)
typescript
import { RouterUtils } from '../utils/RouterUtils';
@Component
struct GoodsItemComponent {
@Prop goods: GoodsModel;
// ...
build() {
Column({ space: 12 }) {
// 商品图片
Image(this.goods.imageUrl)
.width('100%')
.height(200)
.objectFit(ImageFit.Cover)
.borderRadius(12)
.onClick(() => {
RouterUtils.push('pages/ProductDetailPage', { id: this.goods.id });
});
// 商品信息
// ...
}
// ...
}
}
2. 商品详情→购物车
⌨️ entry/src/main/ets/pages/ProductDetailPage.ets(修改)
typescript
import { RouterUtils } from '../utils/RouterUtils';
import { CartService } from '../services/CartService';
@Entry
@Component
struct ProductDetailPage {
@State goods: GoodsModel = goodsData[0];
@State count: number = 1;
build() {
Scroll() {
Column({ space: 24 }) {
// 商品图片
// ...
// 商品信息
Column({ space: 16 }) {
// ...
// 加入购物车与立即购买按钮
Row({ space: 16 }) {
Button('加入购物车')
.width('50%')
.height(48)
.backgroundColor('#FFFFFF')
.textColor('#007DFF')
.border({ width: 1, color: '#007DFF' })
.onClick(() => {
CartService.getInstance().addToCart(this.goods.id, this.count);
RouterUtils.push('pages/CartPage');
});
Button('立即购买')
.width('50%')
.height(48)
.backgroundColor('#007DFF')
.onClick(() => {
RouterUtils.push('pages/OrderPage', {
productId: this.goods.id,
count: this.count
});
});
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween);
}
.width('100%')
.padding(24);
}
.width('100%')
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5');
}
}
3. 购物车→订单
⌨️ entry/src/main/ets/components/CartComponent.ets(修改)
typescript
import { RouterUtils } from '../utils/RouterUtils';
@Component
export struct CartComponent {
@ObjectLink cartItems: Array<CartItemModel> = [];
// 计算选中商品信息
@Computed
get checkedItems(): Array<CartItemModel> {
return this.cartItems.filter(item => item.isChecked);
}
build() {
Column({ space: 0 }) {
// 购物车商品列表
// ...
// 购物车底部栏
Row({ space: 16 }) {
// ...
// 总价与结算按钮
Row({ space: 16 }) {
Text(`¥${this.totalPrice.toFixed(2)}`)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.textColor('#FF0000');
Button('结算')
.width(120)
.height(48)
.backgroundColor('#007DFF')
.onClick(() => {
RouterUtils.push('pages/OrderPage', { cartItems: this.checkedItems });
});
}
.layoutWeight(1)
.justifyContent(FlexAlign.End);
}
.width('100%')
.height(56)
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(16, 16, 0, 0);
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5');
}
}
4. 订单→支付
⌨️ entry/src/main/ets/pages/OrderPage.ets
typescript
import { RouterUtils } from '../utils/RouterUtils';
import { CartService } from '../services/CartService';
import { CartItemModel } from '../components/CartComponent';
@Entry
@Component
struct OrderPage {
@State orderId: string = '123456';
@State orderAmount: number = 0;
@State cartItems: Array<CartItemModel> = [];
build() {
Column({ space: 0 }) {
// 订单信息
List({ space: 16 }) {
ForEach(this.cartItems, (item: CartItemModel) => {
ListItem() {
Row({ space: 16 }) {
Image(item.imageUrl)
.width(80)
.height(80)
.objectFit(ImageFit.Contain);
Column({ space: 8 }) {
Text(item.name)
.fontSize(14)
.fontWeight(FontWeight.Bold)
.textColor('#000000');
Text(`¥${item.price} x ${item.count}`)
.fontSize(14)
.textColor('#666666');
}
.layoutWeight(1);
}
.width('100%')
.height('auto');
}
.width('100%')
.height('auto');
}, (item: CartItemModel) => item.id.toString());
}
.width('100%')
.height('auto')
.padding(16)
.layoutWeight(1);
// 订单总金额与支付按钮
Row({ space: 16 }) {
Text(`总金额:¥${this.orderAmount.toFixed(2)}`)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.textColor('#FF0000');
Button('立即支付')
.width(120)
.height(48)
.backgroundColor('#007DFF')
.onClick(() => {
RouterUtils.push('pages/PaymentPage', {
orderId: this.orderId,
orderAmount: this.orderAmount
});
});
}
.width('100%')
.height(56)
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(16, 16, 0, 0);
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5');
}
aboutToAppear() {
const params = RouterUtils.getParams();
if (params.cartItems) {
this.cartItems = params.cartItems;
this.orderAmount = this.cartItems.reduce((total, item) => total + item.price * item.count, 0);
} else if (params.productId && params.count) {
const product = goodsData.find(item => item.id === params.productId);
if (product) {
this.cartItems = [new CartItemModel(
product.id,
product.name,
product.imageUrl,
product.price,
params.count,
false
)];
this.orderAmount = product.price * params.count;
}
}
}
}
5. 支付→订单
⌨️ entry/src/main/ets/pages/PaymentPage.ets
typescript
import { RouterUtils } from '../utils/RouterUtils';
@Entry
@Component
struct PaymentPage {
@State orderId: string = '';
@State orderAmount: number = 0;
@State paymentMethod: string = 'alipay';
build() {
Column({ space: 0 }) {
// 支付信息
List({ space: 16 }) {
ListItem() {
Text('订单编号:' + this.orderId)
.fontSize(14)
.textColor('#666666');
}
.width('100%')
.height('auto');
ListItem() {
Text('支付金额:¥' + this.orderAmount.toFixed(2))
.fontSize(18)
.fontWeight(FontWeight.Bold)
.textColor('#FF0000');
}
.width('100%')
.height('auto');
}
.width('100%')
.height('auto')
.padding(16)
.layoutWeight(1);
// 支付方式选择
List({ space: 16 }) {
ListItem() {
Row({ space: 16 }) {
Image($r('app.media.alipay'))
.width(40)
.height(40)
.objectFit(ImageFit.Contain);
Text('支付宝')
.fontSize(14)
.textColor('#000000');
Checkbox()
.checked(this.paymentMethod === 'alipay')
.onChange((isChecked: boolean) => {
if (isChecked) {
this.paymentMethod = 'alipay';
}
});
}
.width('100%')
.height('auto');
}
.width('100%')
.height('auto');
ListItem() {
Row({ space: 16 }) {
Image($r('app.media.wechat'))
.width(40)
.height(40)
.objectFit(ImageFit.Contain);
Text('微信支付')
.fontSize(14)
.textColor('#000000');
Checkbox()
.checked(this.paymentMethod === 'wechat')
.onChange((isChecked: boolean) => {
if (isChecked) {
this.paymentMethod = 'wechat';
}
});
}
.width('100%')
.height('auto');
}
.width('100%')
.height('auto');
}
.width('100%')
.height('auto')
.padding(16);
// 确认支付按钮
Button('确认支付')
.width('100%')
.height(48)
.backgroundColor('#007DFF')
.onClick(() => {
// 模拟支付成功
setTimeout(() => {
RouterUtils.popTo('pages/OrderPage', { paymentResult: 'success' });
}, 1000);
});
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5');
}
aboutToAppear() {
const params = RouterUtils.getParams();
if (params.orderId) {
this.orderId = params.orderId;
}
if (params.orderAmount) {
this.orderAmount = params.orderAmount;
}
}
}
三、 组件间通信实战 🔄
3.1 全局状态管理
1. 全局状态定义
⌨️ entry/src/main/ets/utils/GlobalState.ets
typescript
import preferences from '@ohos.data.preferences';
import { UIAbilityContext } from '@ohos.abilityAccessCtrl';
// 全局状态类型
export interface GlobalState {
userInfo: any;
cartCount: number;
theme: 'light' | 'dark';
}
// 全局状态管理
export class GlobalStateManager {
private static instance: GlobalStateManager | null = null;
private dataPreferences: preferences.Preferences | null = null;
private globalState: GlobalState = {
userInfo: null,
cartCount: 0,
theme: 'light'
};
// 单例模式
static getInstance(): GlobalStateManager {
if (!GlobalStateManager.instance) {
GlobalStateManager.instance = new GlobalStateManager();
}
return GlobalStateManager.instance;
}
// 初始化全局状态
async init(context: UIAbilityContext): Promise<void> {
if (!this.dataPreferences) {
this.dataPreferences = await preferences.getPreferences(context, 'app_preferences');
}
this.loadFromPreferences();
}
// 从Preferences加载全局状态
private loadFromPreferences(): void {
this.globalState.userInfo = this.dataPreferences!.getSync('userInfo', null);
this.globalState.cartCount = this.dataPreferences!.getSync('cartCount', 0);
this.globalState.theme = this.dataPreferences!.getSync('theme', 'light');
}
// 保存全局状态到Preferences
async saveToPreferences(): Promise<void> {
await this.dataPreferences!.put('userInfo', this.globalState.userInfo);
await this.dataPreferences!.put('cartCount', this.globalState.cartCount);
await this.dataPreferences!.put('theme', this.globalState.theme);
await this.dataPreferences!.flush();
}
// 获取全局状态
getState(): GlobalState {
return this.globalState;
}
// 更新全局状态
async setState(key: keyof GlobalState, value: any): Promise<void> {
this.globalState[key] = value;
await this.saveToPreferences();
}
}
2. 全局状态组件
⌨️ entry/src/main/ets/components/GlobalStateComponent.ets
typescript
import { GlobalStateManager } from '../utils/GlobalState';
@Component
export struct GlobalStateComponent {
@State globalState: GlobalStateManager['getState']() = GlobalStateManager.getInstance().getState();
build() {
Column({ space: 0 }) {
// 子组件内容
BuilderContainer();
}
.width('100%')
.height('100%')
.backgroundColor(this.globalState.theme === 'light' ? '#F5F5F5' : '#121212');
}
aboutToAppear() {
// 监听全局状态变化
AppStorage.watch('globalState', () => {
this.globalState = GlobalStateManager.getInstance().getState();
});
}
}
3.2 组件间通信
1. 父子组件通信
⌨️ entry/src/main/ets/components/MyPageComponent.ets
typescript
import { GlobalStateManager } from '../utils/GlobalState';
import { RouterUtils } from '../utils/RouterUtils';
@Component
export struct MyPageComponent {
@State userInfo: any = GlobalStateManager.getInstance().getState().userInfo;
@State cartCount: number = GlobalStateManager.getInstance().getState().cartCount;
build() {
Column({ space: 0 }) {
// 用户信息
if (this.userInfo) {
Row({ space: 16 }) {
Image(this.userInfo.avatar)
.width(60)
.height(60)
.objectFit(ImageFit.Contain)
.borderRadius(30);
Column({ space: 8 }) {
Text(this.userInfo.nickname)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.textColor('#000000');
Text(this.userInfo.email)
.fontSize(14)
.textColor('#666666');
}
.layoutWeight(1);
}
.width('100%')
.height('auto')
.padding(16);
}
// 我的订单
Row({ space: 16 }) {
Image($r('app.media.order'))
.width(24)
.height(24)
.objectFit(ImageFit.Contain);
Text('我的订单')
.fontSize(14)
.textColor('#000000');
Image($r('app.media.right'))
.width(24)
.height(24)
.objectFit(ImageFit.Contain);
}
.width('100%')
.height('auto')
.padding(16)
.onClick(() => {
RouterUtils.push('pages/OrderPage');
});
// 我的购物车
Row({ space: 16 }) {
Image($r('app.media.cart'))
.width(24)
.height(24)
.objectFit(ImageFit.Contain);
Text('我的购物车')
.fontSize(14)
.textColor('#000000');
if (this.cartCount > 0) {
Text(`${this.cartCount}`)
.fontSize(12)
.textColor('#FFFFFF')
.backgroundColor('#FF0000')
.width(20)
.height(20)
.textAlign(TextAlign.Center)
.borderRadius(10);
}
Image($r('app.media.right'))
.width(24)
.height(24)
.objectFit(ImageFit.Contain);
}
.width('100%')
.height('auto')
.padding(16)
.onClick(() => {
RouterUtils.push('pages/CartPage');
});
// 登录/退出按钮
if (this.userInfo) {
Button('退出登录')
.width('100%')
.height(48)
.backgroundColor('#FFFFFF')
.textColor('#007DFF')
.border({ width: 1, color: '#007DFF' })
.onClick(() => {
GlobalStateManager.getInstance().setState('userInfo', null);
});
} else {
Button('登录')
.width('100%')
.height(48)
.backgroundColor('#007DFF')
.onClick(() => {
RouterUtils.push('pages/LoginPage');
});
}
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5');
}
aboutToAppear() {
AppStorage.watch('globalState', () => {
this.userInfo = GlobalStateManager.getInstance().getState().userInfo;
this.cartCount = GlobalStateManager.getInstance().getState().cartCount;
});
}
}
2. 跨组件通信
⌨️ entry/src/main/ets/pages/LoginPage.ets
typescript
import { GlobalStateManager } from '../utils/GlobalState';
import { RouterUtils } from '../utils/RouterUtils';
import { UserService, LoginRequestModel } from '../services/UserService';
@Entry
@Component
struct LoginPage {
@State username: string = '';
@State password: string = '';
@State isLoading: boolean = false;
@State errorMessage: string = '';
build() {
Column({ space: 0 }) {
// 登录表单
Column({ space: 16 }) {
Text('登录')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.textColor('#000000');
Input({
text: this.username,
placeholder: '请输入用户名'
})
.width('100%')
.height(48)
.fontSize(14)
.backgroundColor('#FFFFFF')
.borderRadius(8)
.onChange((value: string) => {
this.username = value;
});
Input({
text: this.password,
placeholder: '请输入密码',
type: InputType.Password
})
.width('100%')
.height(48)
.fontSize(14)
.backgroundColor('#FFFFFF')
.borderRadius(8)
.onChange((value: string) => {
this.password = value;
});
if (this.errorMessage) {
Text(this.errorMessage)
.fontSize(14)
.textColor('#FF0000');
}
Button('登录')
.width('100%')
.height(48)
.backgroundColor('#007DFF')
.onClick(() => this.login())
.enabled(!this.isLoading);
}
.width('100%')
.height('auto')
.padding(16);
// 加载动画
if (this.isLoading) {
LoadingComponent({ isLoading: this.isLoading });
}
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5');
}
async login() {
this.isLoading = true;
this.errorMessage = '';
try {
const request: LoginRequestModel = {
username: this.username,
password: this.password
};
const response = await UserService.getInstance().login(request);
// 更新全局状态
GlobalStateManager.getInstance().setState('userInfo', response.userInfo);
RouterUtils.pop();
} catch (error) {
this.errorMessage = error.message;
} finally {
this.isLoading = false;
}
}
}
四、 页面路由高级特性 🔧
4.1 预加载
1. 预加载配置
在「entry/src/main/module.json5」中配置预加载页面:
json
{
"module": {
"pages": [
"pages/Index",
"pages/SearchPage",
"pages/ProductDetailPage",
"pages/CartPage",
"pages/OrderPage",
"pages/PaymentPage",
"pages/LoginPage",
"pages/MyPage"
],
"preloadPages": [
"pages/ProductDetailPage",
"pages/CartPage"
]
}
}
2. 预加载监听
⌨️ entry/src/main/ets/entryability/EntryAbility.ts(修改)
typescript
import { RouterUtils } from '../utils/RouterUtils';
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// 初始化预加载页面
RouterUtils.preload('pages/ProductDetailPage');
RouterUtils.preload('pages/CartPage');
}
// ...
}
4.2 路由动画
1. 动画配置
在「entry/src/main/ets/utils/RouterUtils.ets」中添加动画配置:
typescript
import router from '@ohos.router';
// 页面路由工具类
export class RouterUtils {
// 跳转到指定页面并添加动画
static pushWithAnimation(url: string, params?: RouterParams, animation?: RouterAnimation): void {
router.pushUrl({
url,
params,
animParams: animation || {
duration: 300,
curve: Curve.EaseOut,
type: RouterAnimationType.Translate
}
});
}
// 返回到上一页并添加动画
static popWithAnimation(animation?: RouterAnimation): void {
router.back({
animParams: animation || {
duration: 300,
curve: Curve.EaseOut,
type: RouterAnimationType.Translate
}
});
}
}
2. 动画类型
typescript
// 路由动画类型
export type RouterAnimationType = 'Translate' | 'Scale' | 'Rotate';
// 路由动画配置
export interface RouterAnimation {
duration: number;
curve: Curve;
type: RouterAnimationType;
direction?: 'Left' | 'Right' | 'Top' | 'Bottom';
}
4.3 路由拦截器
1. 拦截器配置
在「entry/src/main/ets/utils/RouterUtils.ets」中添加拦截器:
typescript
import router from '@ohos.router';
import { GlobalStateManager } from '../utils/GlobalState';
// 页面路由工具类
export class RouterUtils {
// 设置路由拦截器
static setInterceptor(): void {
router.setInterceptor({
shouldRouter: (url: string): boolean => {
// 检查是否需要登录
const needLogin = ['pages/OrderPage', 'pages/PaymentPage', 'pages/MyPage'].includes(url);
if (needLogin && !GlobalStateManager.getInstance().getState().userInfo) {
RouterUtils.push('pages/LoginPage', { fromUrl: url });
return false;
}
return true;
},
beforeEach: (url: string): void => {
console.log('路由拦截器:beforeEach - ' + url);
},
afterEach: (url: string): void => {
console.log('路由拦截器:afterEach - ' + url);
}
});
}
}
2. 拦截器初始化
⌨️ entry/src/main/ets/entryability/EntryAbility.ts(修改)
typescript
import { RouterUtils } from '../utils/RouterUtils';
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// 初始化路由拦截器
RouterUtils.setInterceptor();
}
// ...
}
五、 项目运行与效果验证 📱
5.1 项目配置
1. 配置动画资源
在「entry/src/main/resources/base/media」目录下添加动画资源(如旋转动画、缩放动画)。
2. 配置预加载页面
在「entry/src/main/module.json5」中配置预加载页面。
5.2 🔧 项目运行
① 在DevEco Studio中点击「Run」按钮;
② 选择调试设备,点击「OK」;
③ 等待编译安装完成,应用会自动在设备上启动。
效果验证
✅ 页面跳转 :首页→商品详情→购物车→订单→支付的完整页面跳转;
✅ 路由参数传递 :传递商品ID、数量、选中商品信息、订单信息;
✅ 全局状态管理 :用户信息、购物车数量、主题切换;
✅ 路由拦截 :未登录时拦截订单、支付、我的页面跳转,跳转到登录页面;
✅ 预加载:商品详情、购物车页面预加载,提升加载速度。
六、 总结与未来学习路径 🚀
6.1 总结
本文作为《鸿蒙APP开发从入门到精通》的第5篇,完成了:
- 鸿蒙官方页面路由库的配置与使用;
- 完整业务流程的页面跳转实现;
- 组件间通信的原理与实现方式(路由参数、全局状态管理);
- 页面路由的高级特性(预加载、动画、拦截器)。
6.2 未来学习路径
- 第6篇:原子化服务与元服务卡片的开发;
- 第7篇:超级终端多设备协同开发;
- 第8篇:服务联邦跨服务无缝打通;
- 第9篇:安全加固与组件化架构;
- 第10篇:AI原生与用户增长;
- 第11篇:性能优化与Next原生合规;
- 第12篇:运维监控、生态运营与专属变现。
结语 ✅
恭喜你!你已经完成了《鸿蒙APP开发从入门到精通》的第5篇,掌握了页面路由与组件跳转的核心技术。
从现在开始,你已具备了构建复杂应用架构的能力。未来的7篇文章将逐步构建一个完整的鸿蒙电商购物车全栈项目,并最终实现华为应用市场上架变现。
让我们一起期待鸿蒙生态的爆发! 🎉🎉🎉