Quản lý điều hướng trang hiệu quả trong HarmonyOS với Navigation và NavPathStack

Trong hệ sinh thái HarmonyOS NEXT và ArkTS, việc quản lý luồng di chuyển giữa các màn hình đã chuyển dịch từ mô-đun router truyền thống sang thành phần Navigation mang tính khai báo. Sự thay đổi này không chỉ giúp tối ưu hóa hiệu suất mà còn mang lại khả năng kiểm soát trạng thái ứng dụng linh hoạt hơn thông qua NavPathStack.

1. So sánh cơ chế Router và Navigation

Việc hiểu rõ sự khác biệt giữa hai phương thức điều hướng là điều kiện tiên quyết để xây dựng cấu trúc ứng dụng bền vững:

Đặc tính Mô-đun router (Cũ) Thành phần Navigation (Khuyến nghị)
Mô hình lập trình Mệnh lệnh (Imperative) Khai báo (Declarative)
Quản lý trạng thái Nội bộ, khó quan sát Dựa trên NavPathStack, có thể theo dõi trạng thái
Khả năng tùy biến Hạn chế Rất cao (Interception, Custom Animation)
Trường hợp sử dụng Ứng dụng đơn giản Ứng dụng phức tạp, đa tầng lớp

2. Thiết lập cấu trúc định tuyến

Thay vì sử dụng các chuỗi ký tự (hard-coded strings) rải rác, chúng ta nên tập trung quản lý các đường dẫn điều hướng trong một hằng số duy nhất để dễ bảo trì.

// entry/src/main/ets/common/constants/AppRoutes.ts
export enum AppRoutes {
  HOME = 'page/home',
  ARTICLE_DETAIL = 'page/article_detail',
  USER_PROFILE = 'page/profile'
}

3. Triển khai Navigation Container và NavPathStack

Thành phần Navigation đóng vai trò là khung chứa, trong khi NavPathStack quản lý ngăn xếp các trang. Dưới đây là cách thiết lập tại trang chính (Index):

import { NavPathStack, Navigation, NavDestination } from '@kit.ArkUI';
import { AppRoutes } from '../common/constants/AppRoutes';
import { ArticleModel } from '../model/ArticleModel';

@Entry
@Component
struct MainIndex {
  // Khởi tạo ngăn xếp điều hướng
  private navStack: NavPathStack = new NavPathStack();

  @Builder
  MapPage(name: string, param: any) {
    if (name === AppRoutes.ARTICLE_DETAIL) {
      ArticleDetailPage({ data: param })
    }
  }

  build() {
    Column() {
      // Gắn NavPathStack vào Navigation component
      Navigation(this.navStack) {
        Column() {
          Text('Danh sách tin tức')
            .fontSize(22)
            .fontWeight(FontWeight.Bold)
          
          Button('Xem chi tiết bài viết')
            .onClick(() => {
              const mockData: ArticleModel = { id: '101', title: 'Khám phá ArkUI' };
              // Đẩy trang mới vào ngăn xếp cùng tham số
              this.navStack.pushPath({
                name: AppRoutes.ARTICLE_DETAIL,
                param: mockData
              });
            })
        }
        .width('100%')
        .height('100%')
      }
      .navDestination(this.MapPage) // Chỉ định builder để render trang đích
      .title('Trang chủ')
      .mode(NavigationMode.Stack)
    }
    .width('100%')
    .height('100%')
  }
}

4. Tiếp nhận tham số tại trang đích

Tại trang đích (NavDestination), chúng ta có thể nhận dữ liệu thông qua cấu trúc component hoặc truy xuất trực tiếp từ ngăn xếp.

@Component
export struct ArticleDetailPage {
  @Prop data: any; // Nhận tham số truyền từ builder
  private navStack: NavPathStack = new NavPathStack();

  build() {
    NavDestination() {
      Column() {
        Text(`ID bài viết: ${this.data?.id}`)
        Text(`Tiêu đề: ${this.data?.title}`)
          .fontSize(20)
        
        Button('Quay lại')
          .onClick(() => {
            this.navStack.pop(); // Thoát trang hiện tại
          })
      }
      .padding(20)
      .width('100%')
    }
    .title('Chi tiết bài viết')
    .onReady((context: NavDestinationContext) => {
      // Có thể lấy NavPathStack từ context để thực hiện điều hướng tiếp theo
      this.navStack = context.pathStack;
    })
  }
}

5. Cơ chế chặn điều hướng (Routing Interception)

Một trong những ưu điểm lớn của Navigation là khả năng kiểm soát quyền truy cập trang trước khi quá trình chuyển hướng diễn ra.

Navigation(this.navStack) {
  // Content...
}
.onIntercept((target: NavPathStack.Target) => {
  const isUserLoggedIn = false; // Giả lập trạng thái đăng nhập
  
  if (target.name === AppRoutes.USER_PROFILE && !isUserLoggedIn) {
    // Nếu chưa đăng nhập, chuyển hướng sang trang Login thay vì Profile
    this.navStack.pushPath({ name: 'page/login' });
    return true; // Xác nhận việc chặn (intercepted)
  }
  return false; // Cho phép điều hướng bình thường
})

6. Tối ưu hóa hiệu suất điều hướng

Để đảm bảo ứng dụng hoạt động mượt mà và tránh rò rỉ bộ nhớ, lập trình viên cần tuân thủ các nguyên tắc sau:

  • Sử dụng replacePath thay vì pushPath: Khi chuyển từ màn hình đăng nhập sang màn hình chính, hãy dùng replacePath để giải phóng trang đăng nhập khỏi ngăn xếp, ngăn người dùng quay lại trang này bằng nút Back vật lý.
  • Truyền tham số tinh gọn: Tránh truyền các đối tượng JSON quá lớn qua tham số định tuyến. Thay vào đó, chỉ truyền ID và thực hiện truy vấn dữ liệu từ bộ nhớ đệm (như AppStorage) hoặc API tại trang đích.
  • Quản lý vòng đời: Tận dụng các sự kiện onShownonHidden của NavDestination để tạm dừng/khởi động lại các tác vụ tiêu tốn tài nguyên như Animation hoặc Video Playback.

7. Xử lý hoạt ảnh chuyển trang tùy chỉnh

Bên cạnh các hiệu ứng mặc định, ArkUI cung cấp khả năng can thiệp vào quá trình chuyển cảnh thông qua customNavContentTransition.

.customNavContentTransition((from: NavContentInfo, to: NavContentInfo, operation: NavigationOperation) => {
  // Logic xử lý animation dựa trên trang đi (from) và trang đến (to)
  // Ví dụ: Sử dụng hiệu ứng mờ dần (Opacity) hoặc trượt ngang (Translate)
  if (to.name === AppRoutes.ARTICLE_DETAIL) {
    return {
      timeout: 500,
      transition: (proxy: NavigationTransitionProxy) => {
        // Thực hiện animation tại đây
      }
    };
  }
  return undefined;
})

Thẻ: HarmonyOS ArkTS Navigation NavPathStack Mobile development

Đăng vào ngày 3 tháng 8 lúc 12:55