HarmonyOS 5开发从入门到精通(五):页面路由与导航

HarmonyOS 5开发从入门到精通(五):页面路由与导航

一、路由系统基础概念

在HarmonyOS应用开发中,页面路由(Router)是实现多页面跳转和导航的核心机制。路由系统负责管理页面的堆栈、跳转动画、参数传递等关键功能。

1.1 页面路由的基本原理

HarmonyOS的路由系统基于页面堆栈(Page Stack)管理,支持前进、后退、替换、清空等多种操作模式。每个页面都是一个独立的组件,通过路由API进行跳转和通信。

核心特性

  • 支持页面间参数传递
  • 提供多种跳转动画效果
  • 支持页面返回结果回调
  • 内置页面生命周期管理

二、基础路由操作

2.1 页面跳转与返回

import router from '@ohos.router';

// 首页组件
@Entry
@Component
struct HomePage {
  build() {
    Column({ space: 20 }) {
      Text('首页')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
      
      Button('跳转到详情页')
        .onClick(() => {
          // 跳转到详情页
          router.pushUrl({
            url: 'pages/DetailPage',
            params: { id: 123, name: '测试商品' }
          });
        })
        .width(200)
      
      Button('跳转到设置页')
        .onClick(() => {
          router.pushUrl({
            url: 'pages/SettingsPage'
          });
        })
        .width(200)
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .padding(20)
  }
}

// 详情页组件
@Component
struct DetailPage {
  @State id: number = 0
  @State name: string = ''
  
  aboutToAppear() {
    // 接收路由参数
    const params = router.getParams();
    if (params) {
      this.id = params['id'] as number;
      this.name = params['name'] as string;
    }
  }
  
  build() {
    Column({ space: 20 }) {
      Text(`商品ID: ${this.id}`)
        .fontSize(18)
      
      Text(`商品名称: ${this.name}`)
        .fontSize(18)
      
      Button('返回首页')
        .onClick(() => {
          router.back();
        })
        .width(200)
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .padding(20)
  }
}

2.2 路由跳转模式

HarmonyOS支持多种路由跳转模式,满足不同场景需求:

@Component
struct NavigationDemo {
  build() {
    Column({ space: 15 }) {
      // 1. 标准跳转(压入堆栈)
      Button('pushUrl - 标准跳转')
        .onClick(() => {
          router.pushUrl({
            url: 'pages/TargetPage',
            params: { type: 'push' }
          });
        })
        .width(200)
      
      // 2. 替换当前页(不保留历史)
      Button('replaceUrl - 替换当前页')
        .onClick(() => {
          router.replaceUrl({
            url: 'pages/TargetPage',
            params: { type: 'replace' }
          });
        })
        .width(200)
      
      // 3. 清空堆栈并跳转
      Button('clear - 清空堆栈并跳转')
        .onClick(() => {
          router.clear();
          router.pushUrl({
            url: 'pages/TargetPage',
            params: { type: 'clear' }
          });
        })
        .width(200)
      
      // 4. 返回上一页
      Button('back - 返回')
        .onClick(() => {
          router.back();
        })
        .width(200)
      
      // 5. 返回指定页面
      Button('backToPage - 返回指定页')
        .onClick(() => {
          router.backToPage({
            url: 'pages/HomePage'
          });
        })
        .width(200)
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .padding(20)
  }
}

三、路由参数传递

3.1 基本参数传递

// 发送参数
router.pushUrl({
  url: 'pages/UserProfile',
  params: {
    userId: 1001,
    userName: '张三',
    userType: 'vip',
    extraData: {
      age: 25,
      city: '深圳'
    }
  }
});

// 接收参数
@Component
struct UserProfile {
  @State userId: number = 0
  @State userName: string = ''
  @State userType: string = ''
  @State extraData: any = {}
  
  aboutToAppear() {
    const params = router.getParams();
    if (params) {
      this.userId = params['userId'] as number;
      this.userName = params['userName'] as string;
      this.userType = params['userType'] as string;
      this.extraData = params['extraData'] || {};
    }
  }
  
  build() {
    Column({ space: 15 }) {
      Text(`用户ID: ${this.userId}`)
        .fontSize(18)
      
      Text(`用户名: ${this.userName}`)
        .fontSize(18)
      
      Text(`用户类型: ${this.userType}`)
        .fontSize(18)
      
      Text(`年龄: ${this.extraData.age || '未知'}`)
        .fontSize(16)
      
      Text(`城市: ${this.extraData.city || '未知'}`)
        .fontSize(16)
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .padding(20)
  }
}

3.2 返回结果回调

// 跳转并等待返回结果
router.pushUrl({
  url: 'pages/SelectionPage',
  params: { title: '请选择选项' }
}).then(() => {
  // 页面返回时触发
  const result = router.getParams();
  if (result) {
    console.log('返回结果:', result);
  }
});

// 目标页面设置返回结果
@Component
struct SelectionPage {
  @State options: string[] = ['选项A', '选项B', '选项C']
  @State selectedOption: string = ''
  
  build() {
    Column({ space: 15 }) {
      Text('请选择一个选项')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 20 })
      
      ForEach(this.options, (option) => {
        Button(option)
          .onClick(() => {
            this.selectedOption = option;
            // 返回并传递结果
            router.back({
              url: 'pages/SelectionPage',
              params: { selected: option }
            });
          })
          .width(200)
          .margin({ bottom: 10 })
      })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .padding(20)
  }
}

四、路由动画效果

4.1 内置动画效果

HarmonyOS提供了丰富的页面跳转动画效果:

@Component
struct AnimationDemo {
  build() {
    Column({ space: 15 }) {
      // 1. 无动画
      Button('无动画')
        .onClick(() => {
          router.pushUrl({
            url: 'pages/TargetPage',
            animation: {
              type: router.AnimationType.None
            }
          });
        })
        .width(200)
      
      // 2. 默认动画(推入)
      Button('默认推入动画')
        .onClick(() => {
          router.pushUrl({
            url: 'pages/TargetPage',
            animation: {
              type: router.AnimationType.Push,
              duration: 300
            }
          });
        })
        .width(200)
      
      // 3. 淡入淡出
      Button('淡入淡出')
        .onClick(() => {
          router.pushUrl({
            url: 'pages/TargetPage',
            animation: {
              type: router.AnimationType.Fade,
              duration: 400
            }
          });
        })
        .width(200)
      
      // 4. 从底部滑入
      Button('底部滑入')
        .onClick(() => {
          router.pushUrl({
            url: 'pages/TargetPage',
            animation: {
              type: router.AnimationType.Slide,
              curve: router.AnimationCurve.EaseOut
            }
          });
        })
        .width(200)
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .padding(20)
  }
}

4.2 自定义动画曲线

router.pushUrl({
  url: 'pages/TargetPage',
  animation: {
    type: router.AnimationType.Push,
    duration: 500,
    curve: router.AnimationCurve.Spring // 弹簧效果
  }
});

// 或者使用自定义贝塞尔曲线
router.pushUrl({
  url: 'pages/TargetPage',
  animation: {
    type: router.AnimationType.Slide,
    curve: {
      type: router.AnimationCurve.Custom,
      params: [0.42, 0, 0.58, 1] // 贝塞尔曲线参数
    }
  }
});

五、路由守卫与拦截

5.1 页面生命周期钩子

@Component
struct ProtectedPage {
  @State isAuthenticated: boolean = false
  
  // 页面即将显示
  aboutToAppear() {
    console.log('页面即将显示');
    // 检查登录状态
    this.checkAuth();
  }
  
  // 页面显示完成
  onPageShow() {
    console.log('页面显示完成');
  }
  
  // 页面即将隐藏
  aboutToDisappear() {
    console.log('页面即将隐藏');
  }
  
  // 页面隐藏完成
  onPageHide() {
    console.log('页面隐藏完成');
  }
  
  // 检查登录状态
  private checkAuth() {
    const isLoggedIn = AppStorage.Get<boolean>('isLoggedIn') || false;
    if (!isLoggedIn) {
      // 未登录,跳转到登录页
      router.replaceUrl({
        url: 'pages/LoginPage'
      });
      return;
    }
    this.isAuthenticated = true;
  }
  
  build() {
    Column() {
      if (this.isAuthenticated) {
        Text('欢迎访问受保护页面')
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
      } else {
        Text('检查登录状态...')
          .fontSize(16)
          .fontColor('#666')
      }
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

5.2 路由拦截器

import router from '@ohos.router';

// 全局路由拦截器
router.addInterceptor({
  // 路由跳转前拦截
  beforePush: (to: router.RouterOptions, from: router.RouterOptions, next: router.NextCallback) => {
    console.log('路由跳转前:', from?.url, '->', to.url);
    
    // 检查是否需要登录
    if (to.url === 'pages/ProfilePage' && !AppStorage.Get<boolean>('isLoggedIn')) {
      // 跳转到登录页
      router.pushUrl({
        url: 'pages/LoginPage',
        params: { redirect: to.url }
      });
      return;
    }
    
    // 允许跳转
    next();
  },
  
  // 路由返回前拦截
  beforeBack: (to: router.RouterOptions, from: router.RouterOptions, next: router.NextCallback) => {
    console.log('路由返回前:', from?.url, '->', to?.url);
    
    // 确认返回操作
    if (from?.url === 'pages/EditPage') {
      prompt.showDialog({
        title: '确认返回',
        message: '确定要放弃编辑内容吗?',
        buttons: [
          {
            text: '取消',
            color: '#666'
          },
          {
            text: '确定',
            color: '#007DFF',
            action: () => {
              next();
            }
          }
        ]
      });
      return;
    }
    
    next();
  }
});

六、多页面应用实战

6.1 底部导航栏实现

// 底部导航栏组件
@Component
struct BottomTabBar {
  @State currentTab: string = 'home'
  
  private tabs = [
    { key: 'home', name: '首页', icon: $r('app.media.home') },
    { key: 'discover', name: '发现', icon: $r('app.media.discover') },
    { key: 'message', name: '消息', icon: $r('app.media.message') },
    { key: 'profile', name: '我的', icon: $r('app.media.profile') }
  ]
  
  build() {
    Row() {
      ForEach(this.tabs, (tab) => {
        Column() {
          Image(tab.icon)
            .width(24)
            .height(24)
            .objectFit(ImageFit.Contain)
            .opacity(this.currentTab === tab.key ? 1 : 0.5)
          
          Text(tab.name)
            .fontSize(12)
            .fontColor(this.currentTab === tab.key ? '#007DFF' : '#666')
        }
        .width('25%')
        .height(56)
        .justifyContent(FlexAlign.Center)
        .onClick(() => {
          this.currentTab = tab.key;
          this.navigateToTab(tab.key);
        })
      })
    }
    .width('100%')
    .height(56)
    .backgroundColor(Color.White)
    .border({ width: { top: 1 }, color: '#F0F0F0' })
  }
  
  private navigateToTab(tabKey: string) {
    switch (tabKey) {
      case 'home':
        router.replaceUrl({ url: 'pages/HomePage' });
        break;
      case 'discover':
        router.replaceUrl({ url: 'pages/DiscoverPage' });
        break;
      case 'message':
        router.replaceUrl({ url: 'pages/MessagePage' });
        break;
      case 'profile':
        router.replaceUrl({ url: 'pages/ProfilePage' });
        break;
    }
  }
}

// 主页面框架
@Entry
@Component
struct MainApp {
  @State currentPage: string = 'home'
  
  build() {
    Stack() {
      // 页面内容区域
      Column() {
        if (this.currentPage === 'home') {
          HomePage()
        } else if (this.currentPage === 'discover') {
          DiscoverPage()
        } else if (this.currentPage === 'message') {
          MessagePage()
        } else if (this.currentPage === 'profile') {
          ProfilePage()
        }
      }
      .layoutWeight(1)
      
      // 底部导航栏
      Column() {
        Blank()
        BottomTabBar()
      }
      .alignItems(HorizontalAlign.Center)
    }
    .width('100%')
    .height('100%')
  }
}

6.2 路由堆栈管理

// 获取当前路由信息
const currentRoute = router.getState();
console.log('当前路由:', currentRoute);

// 获取路由堆栈
const stack = router.getLength();
console.log('路由堆栈长度:', stack);

// 清空路由历史
router.clear();

// 监听路由变化
router.on('routeChange', (to: router.RouterState, from: router.RouterState) => {
  console.log('路由变化:', from?.name, '->', to.name);
});

// 移除路由监听
router.off('routeChange');

七、路由最佳实践

7.1 路由配置管理

// routes.ts - 路由配置文件
export const ROUTES = {
  HOME: 'pages/HomePage',
  LOGIN: 'pages/LoginPage',
  PROFILE: 'pages/ProfilePage',
  SETTINGS: 'pages/SettingsPage',
  DETAIL: 'pages/DetailPage',
  EDIT: 'pages/EditPage'
} as const;

// 路由跳转工具函数
export class RouterUtils {
  // 跳转到首页
  static toHome() {
    router.replaceUrl({ url: ROUTES.HOME });
  }
  
  // 跳转到登录页
  static toLogin(redirect?: string) {
    router.pushUrl({
      url: ROUTES.LOGIN,
      params: { redirect }
    });
  }
  
  // 跳转到详情页
  static toDetail(id: number, name: string) {
    router.pushUrl({
      url: ROUTES.DETAIL,
      params: { id, name }
    });
  }
  
  // 返回上一页
  static back() {
    router.back();
  }
  
  // 返回首页
  static backToHome() {
    router.backToPage({ url: ROUTES.HOME });
  }
}

7.2 性能优化建议

  1. 避免频繁的路由跳转
// 不推荐:频繁跳转
@State count: number = 0

onClick() {
  this.count++;
  if (this.count > 5) {
    router.pushUrl({ url: 'pages/TargetPage' });
  }
}

// 推荐:防抖处理
private debounceTimer: number = 0

onClick() {
  this.count++;
  clearTimeout(this.debounceTimer);
  this.debounceTimer = setTimeout(() => {
    if (this.count > 5) {
      router.pushUrl({ url: 'pages/TargetPage' });
    }
  }, 300);
}
  1. 合理使用路由缓存
// 在页面组件中缓存数据
@Component
struct CachedPage {
  @State cachedData: any = null
  
  aboutToAppear() {
    if (!this.cachedData) {
      // 首次加载数据
      this.loadData();
    }
  }
  
  private loadData() {
    // 模拟网络请求
    setTimeout(() => {
      this.cachedData = { id: 1, name: '缓存数据' };
    }, 1000);
  }
}
  1. 页面懒加载
// 使用动态导入实现懒加载
const LazyComponent = lazy(() => import('./LazyPage'));

@Entry
@Component
struct MainPage {
  build() {
    Column() {
      Button('加载懒加载页面')
        .onClick(() => {
          router.pushUrl({ url: 'pages/LazyPage' });
        })
    }
  }
}

八、总结

通过本篇教程,您已经掌握了:

路由系统的基本原理和核心API

页面跳转与参数传递的各种方式

路由动画效果的配置和使用

路由守卫与拦截的实现方法

多页面应用的架构设计

性能优化和最佳实践

关键知识点回顾

  • 使用router.pushUrl进行页面跳转,router.back返回
  • 通过router.getParams获取路由参数
  • 支持多种跳转动画效果和自定义曲线
  • 页面生命周期钩子用于权限控制和数据加载
  • 路由拦截器实现全局路由守卫

下一篇我们将学习网络请求与数据管理,掌握如何从服务器获取数据并在应用中展示。建议您动手实践本文中的路由案例,特别是多页面应用和路由守卫部分,这将帮助您深入理解HarmonyOS路由系统的各种特性和使用场景。

posted @ 2025-12-23 18:08  奇崽  阅读(0)  评论(0)    收藏  举报