Flutter 路由革命:GoRouter 完全指南

告别复杂的 Navigator 2.0,拥抱简单强大的 GoRouter

📖 前言:为什么选择 GoRouter?

在 Flutter 2.0 引入 Navigator 2.0 后,路由系统变得更强大但更复杂。开发者需要实现 RouterDelegate、RouteInformationParser 等类,代码量剧增。这时,GoRouter 应运而生,它是由 Flutter 团队维护的官方推荐路由库,旨在简化 Navigator 2.0 的复杂性。

传统方案 vs GoRouter

// ❌ 传统 Navigator 2.0(需要 100+ 行代码)
MaterialApp.router(
  routerDelegate: MyRouterDelegate(),  // 需要自己实现
  routeInformationParser: MyParser(),  // 需要自己实现
)

// ✅ GoRouter(只需 20-30 行代码)
MaterialApp.router(
  routerConfig: goRouter,  // 一行搞定!
)

🚀 快速开始

  1. 安装依赖
# pubspec.yaml
dependencies:
  go_router: ^12.0.0  # 使用最新版本
  1. 基础配置
import 'package:go_router/go_router.dart';

// 创建 GoRouter 实例
final goRouter = GoRouter(
  // 初始路径
  initialLocation: '/',
  
  // 路由配置
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => const HomeScreen(),
    ),
    GoRoute(
      path: '/details/:id',
      builder: (context, state) => DetailsScreen(
        id: state.params['id']!,
      ),
    ),
  ],
  
  // 错误页面
  errorBuilder: (context, state) => ErrorScreen(
    error: state.error,
  ),
);
  1. 集成到应用
void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  final GoRouter _router = goRouter;
  
  
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerConfig: _router,  // 关键配置
      theme: ThemeData(primarySwatch: Colors.blue),
    );
  }
}

✨ 核心特性

  1. 声明式路由配置

GoRouter 使用简洁的声明式 API 定义路由:

final router = GoRouter(
  routes: [
    // 基础路由
    GoRoute(
      path: '/',
      name: 'home',  // 可选命名
      builder: (context, state) => HomeScreen(),
    ),
    
    // 带参数的路由
    GoRoute(
      path: '/user/:userId/profile',
      name: 'profile',
      builder: (context, state) {
        final userId = state.params['userId']!;
        final tab = state.queryParams['tab'] ?? 'info';
        return ProfileScreen(userId: userId, activeTab: tab);
      },
    ),
    
    // 嵌套路由
    GoRoute(
      path: '/dashboard',
      builder: (context, state) => DashboardScreen(),
      routes: [
        GoRoute(
          path: 'analytics',
          builder: (context, state) => AnalyticsScreen(),
        ),
        GoRoute(
          path: 'settings',
          builder: (context, state) => SettingsScreen(),
        ),
      ],
    ),
  ],
);
  1. 路由参数传递

GoRouter 自动解析 URL 参数:

// 路径参数
// URL: /product/123
GoRoute(
  path: '/product/:id',
  builder: (context, state) {
    final id = state.params['id']!;  // "123"
    return ProductScreen(id: id);
  },
)

// 查询参数
// URL: /search?q=flutter&sort=desc
GoRoute(
  path: '/search',
  builder: (context, state) {
    final query = state.queryParams['q'];  // "flutter"
    final sort = state.queryParams['sort']; // "desc"
    return SearchScreen(query: query, sort: sort);
  },
)

// 额外参数(不显示在 URL 中)
context.push('/product/123', extra: Product(
  id: '123',
  name: 'Flutter Guide',
  price: 99.99,
));
// 在目标页面获取
final product = state.extra as Product;
  1. 导航操作
// 1. 在 BuildContext 中使用
class HomeScreen extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          children: [
            // 页面跳转
            ElevatedButton(
              onPressed: () {
                // 普通跳转
                context.go('/details/123');
                
                // 或使用命名路由
                context.goNamed('profile', 
                  params: {'userId': '456'},
                  queryParams: {'tab': 'settings'},
                );
              },
              child: Text('跳转到详情'),
            ),
            
            // 带返回值的跳转
            ElevatedButton(
              onPressed: () async {
                final result = await context.push<String>('/picker');
                if (result != null) {
                  print('选择结果: $result');
                }
              },
              child: Text('选择项目'),
            ),
            
            // 替换当前页面
            ElevatedButton(
              onPressed: () {
                context.replace('/new-page');
              },
              child: Text('替换页面'),
            ),
          ],
        ),
      ),
    );
  }
}

// 2. 在非 Widget 类中使用
class NavigationService {
  final GoRouter router;
  
  NavigationService(this.router);
  
  void navigateToProduct(String id) {
    router.go('/product/$id');
  }
}
  1. 路由守卫与权限控制

GoRouter 内置强大的重定向功能:

final goRouter = GoRouter(
  routes: [...],
  
  // 全局重定向逻辑
  redirect: (context, state) {
    // 获取认证状态
    final authService = context.read<AuthService>();
    final isAuthenticated = authService.isLoggedIn;
    final isAdmin = authService.isAdmin;
    
    // 当前访问的路径
    final location = state.matchedLocation;
    final goingToLogin = location == '/login';
    final goingToAdmin = location.startsWith('/admin');
    
    // 路由守卫逻辑
    if (!isAuthenticated && !goingToLogin) {
      // 未登录且不是去登录页 -> 跳转到登录
      return '/login?from=${Uri.encodeComponent(location)}';
    }
    
    if (isAuthenticated && goingToLogin) {
      // 已登录但要去登录页 -> 跳转到首页
      return '/';
    }
    
    if (goingToAdmin && !isAdmin) {
      // 访问管理员页面但没有权限 -> 无权限页面
      return '/forbidden';
    }
    
    // 允许访问
    return null;
  },
  
  // 监听状态变化(配合 Riverpod/Provider)
  refreshListenable: GoRouterRefreshStream(authService.authChanges),
);

// 页面级别的守卫
GoRoute(
  path: '/admin/dashboard',
  builder: (context, state) => AdminDashboard(),
  redirect: (context, state) {
    final user = context.read<UserService>().currentUser;
    if (user?.role != 'admin') {
      return '/unauthorized';
    }
    return null;
  },
)
  1. 嵌套导航与 ShellRoute

使用 ShellRoute 实现复杂的 UI 布局:

final goRouter = GoRouter(
  routes: [
    // 底部导航栏布局
    ShellRoute(
      builder: (context, state, child) {
        // 返回带有底部导航栏的 Scaffold
        return Scaffold(
          body: child,
          bottomNavigationBar: BottomNavigationBar(
            currentIndex: _calculateCurrentIndex(state),
            items: const [
              BottomNavigationBarItem(
                icon: Icon(Icons.home),
                label: '首页',
              ),
              BottomNavigationBarItem(
                icon: Icon(Icons.search),
                label: '搜索',
              ),
              BottomNavigationBarItem(
                icon: Icon(Icons.person),
                label: '我的',
              ),
            ],
            onTap: (index) {
              // 处理导航栏点击
              switch (index) {
                case 0:
                  context.go('/home');
                  break;
                case 1:
                  context.go('/search');
                  break;
                case 2:
                  context.go('/profile');
                  break;
              }
            },
          ),
        );
      },
      routes: [
        // 首页模块
        GoRoute(
          path: '/home',
          builder: (context, state) => const HomeTab(),
          routes: [
            GoRoute(
              path: 'details/:id',
              builder: (context, state) => HomeDetails(
                id: state.params['id']!,
              ),
            ),
          ],
        ),
        
        // 搜索模块
        GoRoute(
          path: '/search',
          builder: (context, state) => const SearchTab(),
        ),
        
        // 个人中心模块
        GoRoute(
          path: '/profile',
          builder: (context, state) => const ProfileTab(),
        ),
      ],
    ),
  ],
);
  1. 深度链接与 Web 支持

GoRouter 完美支持深度链接和 Web URL:

final goRouter = GoRouter(
  routes: [
    GoRoute(
      path: '/product/:id',
      builder: (context, state) => ProductScreen(
        id: state.params['id']!,
      ),
      // 页面路由信息(用于 Web)
      pageBuilder: (context, state) {
        return MaterialPage(
          key: state.pageKey,
          child: ProductScreen(id: state.params['id']!),
          // 自定义过渡动画
          fullscreenDialog: false,
        );
      },
    ),
  ],
  
  // Web 特定配置
  redirect: (context, state) {
    // 处理 Web 端的特殊逻辑
    if (kIsWeb) {
      // Web 特定的重定向逻辑
    }
    return null;
  },
);

// 测试深度链接
// 在终端中运行:
// flutter run -d chrome --dart-define=FLUTTER_WEB=true
// 然后访问:http://localhost:port/#/product/123
  1. 与状态管理集成(Riverpod)
// 1. 创建基于 Riverpod 的 GoRouter
final goRouterProvider = Provider<GoRouter>((ref) {
  // 监听认证状态
  final authState = ref.watch(authProvider);
  
  return GoRouter(
    routes: [...],
    
    // 响应式的重定向
    redirect: (context, state) {
      final isAuthenticated = authState is Authenticated;
      
      if (state.matchedLocation == '/profile' && !isAuthenticated) {
        return '/login';
      }
      
      return null;
    },
    
    // 当 authState 变化时刷新路由
    refreshListenable: GoRouterRefreshStream(
      ref.watch(authProvider.notifier).stream,
    ),
  );
});

// 2. 在应用中使用
class MyApp extends ConsumerWidget {
  
  Widget build(BuildContext context, WidgetRef ref) {
    final router = ref.watch(goRouterProvider);
    
    return MaterialApp.router(
      routerConfig: router,
      theme: ThemeData(primarySwatch: Colors.blue),
    );
  }
}

🎯 高级技巧

  1. 自定义页面过渡动画
GoRoute(
  path: '/details/:id',
  pageBuilder: (context, state) {
    return CustomTransitionPage(
      key: state.pageKey,
      child: DetailsScreen(id: state.params['id']!),
      transitionsBuilder: (context, animation, secondaryAnimation, child) {
        // 自定义过渡效果
        return FadeTransition(
          opacity: CurveTween(curve: Curves.easeInOut).animate(animation),
          child: child,
        );
      },
      transitionDuration: const Duration(milliseconds: 300),
    );
  },
)
  1. 路由观察与分析
final goRouter = GoRouter(
  routes: [...],
  
  // 路由观察者
  observers: [
    GoRouterObserver(),  // 内置观察者
    
    // 自定义观察者
    _RouteAnalyticsObserver(),
  ],
);

class _RouteAnalyticsObserver extends NavigatorObserver {
  
  void didPush(Route route, Route? previousRoute) {
    super.didPush(route, previousRoute);
    
    // 记录页面访问
    _logPageView(route.settings.name);
  }
  
  void _logPageView(String? pageName) {
    print('📊 页面访问: $pageName');
    // 发送到分析平台
  }
}
  1. 测试路由
import 'package:flutter_test/flutter_test.dart';
import 'package:go_router/go_router.dart';

void main() {
  testWidgets('测试路由跳转', (tester) async {
    // 创建测试用的 GoRouter
    final router = GoRouter(
      routes: [
        GoRoute(
          path: '/',
          builder: (context, state) => const HomeScreen(),
        ),
        GoRoute(
          path: '/details/:id',
          builder: (context, state) => DetailsScreen(
            id: state.params['id']!,
          ),
        ),
      ],
    );
    
    await tester.pumpWidget(
      MaterialApp.router(
        routerConfig: router,
      ),
    );
    
    // 测试导航
    router.go('/details/123');
    await tester.pumpAndSettle();
    
    // 验证页面
    expect(find.text('详情页面 123'), findsOneWidget);
  });
}

📊 GoRouter vs 其他方案对比

特性 GoRouter 原生 Navigator 2.0 AutoRoute Beamer
学习曲线 🟢 平缓 🔴 陡峭 🟡 中等 🟡 中等
代码量 🟢 极少 🔴 很多 🟡 中等 🟡 中等
类型安全 🟢 优秀 🟡 一般 🟢 优秀 🟡 一般
深度链接 🟢 内置 🟡 需要实现 🟢 内置 🟢 内置
Web 支持 🟢 优秀 🟡 需要配置 🟢 优秀 🟢 优秀
路由守卫 🟢 强大 🟡 需要实现 🟢 强大 🟢 强大
嵌套导航 🟢 ShellRoute 🔴 复杂 🟢 优秀 🟡 一般
维护团队 🟢 Flutter 团队 🟢 Flutter 团队 🟡 社区 🟡 社区
文档质量 🟢 优秀 🟡 一般 🟢 良好 🟡 一般

🚨 常见问题与解决方案

Q1: 如何处理返回按钮?

// GoRouter 自动处理返回栈
// 无需额外代码!

// 如果需要自定义返回逻辑:
WillPopScope(
  onWillPop: () async {
    // 自定义返回逻辑
    if (shouldPreventBack) {
      return false; // 阻止返回
    }
    return true; // 允许返回
  },
  child: YourScreen(),
)

Q2: 如何传递复杂对象?

// 使用 extra 参数
context.push('/details', extra: yourObject);

// 在目标页面获取
final object = state.extra as YourObject;

// 注意:extra 不会体现在 URL 中
// 不适合需要分享的链接

Q3: 如何实现页面刷新?

// 方法1:使用 refreshListenable
final goRouter = GoRouter(
  refreshListenable: GoRouterRefreshStream(yourChangeNotifier),
);

// 方法2:手动刷新
final router = GoRouter.of(context);
router.refresh();

Q4: 如何调试路由问题?

final goRouter = GoRouter(
  debugLogDiagnostics: true,  // 启用调试日志
  
  // 错误页面
  errorBuilder: (context, state) {
    // 显示详细错误信息
    return Scaffold(
      body: Center(
        child: Column(
          children: [
            Text('路由错误: ${state.error}'),
            Text('路径: ${state.matchedLocation}'),
            ElevatedButton(
              onPressed: () => context.go('/'),
              child: Text('返回首页'),
            ),
          ],
        ),
      ),
    );
  },
);

📈 性能最佳实践

  1. 懒加载页面
GoRoute(
  path: '/heavy-page',
  builder: (context, state) {
    return FutureBuilder(
      future: import('heavy_page.dart'),
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          return snapshot.data!;
        }
        return const CircularProgressIndicator();
      },
    );
  },
)
  1. 使用页面缓存
GoRoute(
  path: '/cached-page',
  pageBuilder: (context, state) {
    return MaterialPage(
      key: const ValueKey('cached-page'),
      child: const CachedPage(),
      maintainState: true,  // 保持状态
      fullscreenDialog: false,
    );
  },
)
  1. 避免不必要的重定向
// ❌ 错误的做法:每次都会执行重定向逻辑
redirect: (context, state) {
  // 复杂的重定向逻辑...
}

// ✅ 正确的做法:缓存结果
final _redirectCache = <String, String?>{};

redirect: (context, state) {
  final location = state.matchedLocation;
  
  if (_redirectCache.containsKey(location)) {
    return _redirectCache[location];
  }
  
  final result = _calculateRedirect(state);
  _redirectCache[location] = result;
  return result;
}

🎉 总结:为什么选择 GoRouter?

适合 GoRouter 的场景:

  1. 快速开发:需要快速搭建路由系统
  2. Web 应用:需要良好的 URL 支持
  3. 中型应用:路由逻辑不算特别复杂
  4. 团队协作:API 简单,易于理解和维护
  5. 官方支持:需要长期稳定的维护

可能需要其他方案的场景:

  1. 超大型企业应用:可能需要更精细的控制(考虑 Navigator 2.0)
  2. 极致的类型安全:AutoRoute 可能更合适
  3. 特殊的路由需求:需要完全自定义的路由逻辑

迁移建议:

如果你正在使用:

· Navigator 1.0:立即迁移到 GoRouter
· Navigator 2.0:评估复杂性,简化代码
· 其他第三方库:比较特性,按需迁移

📚 学习资源

  1. 官方文档:https://pub.dev/packages/go_router
  2. 示例项目:https://github.com/flutter/packages/tree/main/packages/go_router/example
  3. 视频教程:Flutter 官方 YouTube 频道
  4. 社区讨论:Flutter 中文社区、Stack Overflow

🔮 未来展望

GoRouter 正在快速发展,未来可能会:

· 更好的 DevTools 集成
· 更多的内置过渡动画
· 更强大的嵌套路由支持
· 更好的 TypeScript 风格类型推断


立即开始:

flutter pub add go_router

开始享受简单、强大、高效的 Flutter 路由体验吧!🚀

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐