Flutter 2.0 引入的 Navigator 2.0(Router API)彻底革新了路由管理模式,它基于声明式编程范式,完美解决了传统命令式路由在深层链接、状态同步和路由拦截等场景下的局限性。本文将系统对比两种路由系统,带你从核心概念到实战应用,全面掌握 Navigator 2.0!

一、Navigator 1.0 vs Navigator 2.0 深度对比
特性 Navigator 1.0 Navigator 2.0
编程范式 命令式(Imperative) 声明式(Declarative)
路由控制 手动调用 push/pop,状态分散 基于状态驱动,路由栈与状态同步
深层链接 原生支持有限,实现复杂 原生支持,可通过 URL 直接导航
路由拦截 需自定义实现,侵入性强 集中式管理,拦截逻辑清晰
状态可预测性 弱,路由栈状态不可追踪 强,状态单一数据源
测试友好性 难,需模拟用户操作 易,可直接操作路由状态
二、Navigator 2.0 核心组件解析

Navigator 2.0 采用责任链模式设计,主要由以下核心组件协同工作:

  1. RouteInformationParser

    • 负责解析路由信息(如 URL 字符串)为类型安全的路由配置对象
    • 同时也能将路由配置对象还原为路由信息(用于更新浏览器地址栏)
  2. RouterDelegate

    • 核心组件,管理应用的路由栈状态
    • 根据路由配置构建 Navigator 和页面栈
    • 实现与系统导航的交互(如返回按钮)
  3. RouteInformationProvider

    • 提供路由信息来源(如浏览器 URL、系统 DeepLink)
    • 默认使用PlatformRouteInformationProvider
  4. Page

    • 路由页描述对象,替代传统的MaterialPageRoute
    • 常用实现:MaterialPageCupertinoPagePage
三、实战:构建健壮的路由管理系统
1. 路由配置设计

dart

// lib/router/app_router.dart
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import '../pages/home_page.dart';
import '../pages/detail_page.dart';
import '../pages/login_page.dart';
import '../pages/not_found_page.dart';

/// 路由名称常量
@immutable
class RoutePaths {
  static const String home = '/';
  static const String detail = '/detail';
  static const String login = '/login';
  
  // 私有化构造函数,防止实例化
  const RoutePaths._();
}

/// 路由配置数据类
@immutable
class AppRouteConfig {
  final String path;
  final dynamic arguments;

  const AppRouteConfig({
    required this.path,
    this.arguments,
  });

  // 用于比较,确保Page key的唯一性
  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is AppRouteConfig &&
          runtimeType == other.runtimeType &&
          path == other.path &&
          arguments == other.arguments;

  @override
  int get hashCode => path.hashCode ^ arguments.hashCode;
}
2. 实现 RouterDelegate

dart

// 继续在app_router.dart中添加
class AppRouterDelegate extends RouterDelegate<AppRouteConfig>
    with ChangeNotifier, PopNavigatorRouterDelegateMixin<AppRouteConfig> {
  // 路由栈 - 使用配置对象而非Page,更易管理
  final List<AppRouteConfig> _routeStack = [];
  
  // 全局NavigatorKey
  @override
  final GlobalKey<NavigatorState> navigatorKey;

  AppRouterDelegate() : navigatorKey = GlobalKey<NavigatorState>() {
    // 初始化路由栈
    _routeStack.add(const AppRouteConfig(path: RoutePaths.home));
  }

  // 获取当前路由配置
  @override
  AppRouteConfig get currentConfiguration => 
      _routeStack.isNotEmpty ? _routeStack.last : const AppRouteConfig(path: RoutePaths.home);

  // 构建Navigator
  @override
  Widget build(BuildContext context) {
    return Navigator(
      key: navigatorKey,
      pages: _buildPages(),
      onPopPage: _handlePopPage,
      observers: [HeroController()], // 添加Hero动画支持
    );
  }

  // 构建页面栈
  List<Page> _buildPages() {
    return _routeStack.map((config) {
      switch (config.path) {
        case RoutePaths.home:
          return const MaterialPage(
            key: ValueKey(RoutePaths.home),
            child: HomePage(),
          );
        case RoutePaths.login:
          return const MaterialPage(
            key: ValueKey(RoutePaths.login),
            child: LoginPage(),
          );
        case RoutePaths.detail:
          return MaterialPage(
            key: ValueKey('${RoutePaths.detail}-${config.arguments}'),
            child: DetailPage(id: config.arguments as String),
          );
        default:
          return const MaterialPage(
            key: ValueKey('not-found'),
            child: NotFoundPage(),
          );
      }
    }).toList();
  }

  // 处理页面返回
  bool _handlePopPage(Route<dynamic> route, dynamic result) {
    if (!route.didPop(result)) return false;
    
    if (_routeStack.length > 1) {
      _routeStack.removeLast();
      notifyListeners();
      return true;
    }
    
    // 最后一页,返回false让系统处理(如退出应用)
    return false;
  }

  // 设置新路由
  @override
  Future<void> setNewRoutePath(AppRouteConfig configuration) async {
    // 清空栈并设置新路由(用于深层链接)
    _routeStack.clear();
    _routeStack.add(configuration);
    notifyListeners();
  }

  // 导航方法 - 页面跳转
  void navigateTo(AppRouteConfig config) {
    // 避免重复添加相同页面
    if (_routeStack.last == config) return;
    
    _routeStack.add(config);
    notifyListeners();
  }

  // 返回上一页
  void pop() {
    if (_routeStack.length > 1) {
      _routeStack.removeLast();
      notifyListeners();
    }
  }

  // 替换当前路由
  void replace(AppRouteConfig config) {
    if (_routeStack.isNotEmpty) {
      _routeStack.removeLast();
    }
    _routeStack.add(config);
    notifyListeners();
  }

  // 返回到根路由
  void popToRoot() {
    if (_routeStack.length > 1) {
      _routeStack.removeRange(1, _routeStack.length);
      notifyListeners();
    }
  }
}
3. 实现 RouteInformationParser

dart

// 继续在app_router.dart中添加
class AppRouteInformationParser extends RouteInformationParser<AppRouteConfig> {
  @override
  Future<AppRouteConfig> parseRouteInformation(
      RouteInformation routeInformation) async {
    final uri = Uri.parse(routeInformation.location ?? RoutePaths.home);
    
    // 处理详情页路由参数
    if (uri.path == RoutePaths.detail) {
      final id = uri.queryParameters['id'];
      if (id != null) {
        return AppRouteConfig(
          path: RoutePaths.detail,
          arguments: id,
        );
      }
    }
    
    // 匹配已知路由
    if ([RoutePaths.home, RoutePaths.login, RoutePaths.detail].contains(uri.path)) {
      return AppRouteConfig(path: uri.path);
    }
    
    // 未知路由返回404页面
    return const AppRouteConfig(path: '/404');
  }

  @override
  RouteInformation restoreRouteInformation(AppRouteConfig configuration) {
    // 将路由配置转换回URL
    if (configuration.path == RoutePaths.detail && configuration.arguments != null) {
      return RouteInformation(
        location: '${RoutePaths.detail}?id=${configuration.arguments}',
      );
    }
    
    return RouteInformation(location: configuration.path);
  }
}
4. 配置应用入口

dart

// lib/main.dart
import 'package:flutter/material.dart';
import 'router/app_router.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: 'Navigator 2.0 Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
        useMaterial3: true, // 使用Material 3
      ),
      // 路由信息解析器
      routeInformationParser: AppRouteInformationParser(),
      // 路由代理
      routerDelegate: AppRouterDelegate(),
      // 路由信息提供者(可选,使用默认实现)
      routeInformationProvider: PlatformRouteInformationProvider(
        initialRouteInformation: const RouteInformation(location: RoutePaths.home),
      ),
    );
  }
}
5. 页面实现示例

dart

// lib/pages/home_page.dart
import 'package:flutter/material.dart';
import '../router/app_router.dart';

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    final router = Router.of(context).routerDelegate as AppRouterDelegate;
    
    return Scaffold(
      appBar: AppBar(title: const Text('首页')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: () {
                // 跳转到详情页
                router.navigateTo(
                  const AppRouteConfig(
                    path: RoutePaths.detail,
                    arguments: '1001',
                  ),
                );
              },
              child: const Text('跳转到详情页'),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                // 跳转到登录页
                router.navigateTo(
                  const AppRouteConfig(path: RoutePaths.login),
                );
              },
              child: const Text('跳转到登录页'),
            ),
          ],
        ),
      ),
    );
  }
}

// lib/pages/detail_page.dart
import 'package:flutter/material.dart';
import '../router/app_router.dart';

class DetailPage extends StatelessWidget {
  final String id;
  
  const DetailPage({super.key, required this.id});

  @override
  Widget build(BuildContext context) {
    final router = Router.of(context).routerDelegate as AppRouterDelegate;
    
    return Scaffold(
      appBar: AppBar(
        title: Text('详情页 #$id'),
        leading: IconButton(
          icon: const Icon(Icons.arrow_back),
          onPressed: router.pop,
        ),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('商品ID:$id', style: Theme.of(context).textTheme.headlineMedium),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: router.popToRoot,
              child: const Text('返回首页'),
            ),
          ],
        ),
      ),
    );
  }
}
四、高级特性:路由守卫与拦截

dart

// 在AppRouterDelegate中添加路由守卫逻辑
import 'package:shared_preferences/shared_preferences.dart';

// 添加状态管理
bool _isAuthenticated = false;

// 初始化认证状态
Future<void> _initAuthState() async {
  final prefs = await SharedPreferences.getInstance();
  _isAuthenticated = prefs.getString('token') != null;
}

// 修改navigateTo方法,添加路由守卫
void navigateTo(AppRouteConfig config) async {
  // 初始化认证状态(实际项目中应在应用启动时初始化)
  await _initAuthState();
  
  // 需要登录的路由列表
  final requireAuthRoutes = [RoutePaths.detail];
  
  // 路由守卫逻辑
  if (requireAuthRoutes.contains(config.path) && !_isAuthenticated) {
    // 跳转到登录页,并保存目标路由
    replace(AppRouteConfig(
      path: RoutePaths.login,
      arguments: config, // 保存目标路由
    ));
    return;
  }
  
  // 原有逻辑
  if (_routeStack.last == config) return;
  _routeStack.add(config);
  notifyListeners();
}
五、Navigator 2.0 最佳实践
  1. 状态与 UI 分离

    • 使用独立的数据类管理路由配置,而非直接操作 Page 对象
    • 保持 RouterDelegate 的单一职责:管理路由栈和构建 Navigator
  2. 类型安全

    • 使用常量定义路由路径,避免硬编码
    • 为路由参数创建类型安全的模型类
  3. 错误处理

    • 实现 404 页面处理未知路由
    • 为路由参数解析添加异常处理
  4. 性能优化

    • 使用const构造函数优化不可变对象
    • 合理实现==hashCode,避免不必要的重建
  5. 测试策略

    • 直接测试 RouterDelegate 的状态转换逻辑
    • 使用 Mock 测试路由解析器
六、Navigator 2.0 生态工具
  • auto_route: 自动生成类型安全的路由代码
  • go_router: Flutter 官方推荐的路由库,基于 Navigator 2.0 封装
  • beamer: 功能强大的路由管理库,支持嵌套路由

总结

Navigator 2.0 带来了以下核心价值:

  1. 声明式路由管理:通过状态驱动 UI,实现路由栈的可预测性和可维护性
  2. 完整的路由生命周期:支持深层链接、浏览器历史记录同步等高级特性
  3. 集中式路由控制:便于实现路由守卫、权限控制等横切关注点
  4. 更好的架构设计:促进关注点分离,使代码更具可测试性和可扩展性

通过本文的系统化学习,你应该能够理解 Navigator 2.0 的设计思想,并能在实际项目中构建健壮、可维护的路由系统。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新

更多推荐