Skip to content

go_router 路由

一句话理解

go_router 是 Flutter 官方推荐的路由方案——用声明式的方式写路由,天然支持 Web 和深度链接。

适合新项目直接使用,不必再学 Navigator 的命令式写法。

核心概念

声明式路由 vs 命令式路由

命令式(Navigator)声明式(go_router)
思路在代码中手动调用 push/pop提前定义路由表,框架自动处理
比喻自己开车去每个目的地提前规划好地图,导航仪带你走
代码风格Navigator.push(context, MaterialPageRoute(...))context.push('/detail')

简单说:声明式就是先定义好"有哪些路径、每个路径对应什么页面",然后只管跳路径就行。

GoRoute

GoRoute 是路由表中的一条规则,包含 path(路径)和 builder(构建页面):

dart
GoRoute(
  path: '/detail',           // 路径
  builder: (context, state) => const DetailPage(),  // 对应的页面
),

GoRouterState

builder 的第二个参数 stateGoRouterState,携带了跳转时的所有信息:

属性含义
state.pathParameters路径参数(如 /user/:id 中的 id
state.uri.queryParameters查询参数(如 ?keyword=flutter
state.extra额外传递的对象
state.matchedLocation当前匹配到的路径

MaterialApp.router

使用 go_router 时,必须用 MaterialApp.router 而不是普通的 MaterialApp

dart
// ❌ 普通方式(不支持 go_router)
MaterialApp(home: HomePage());

// ✅ go_router 方式
MaterialApp.router(routerConfig: router);

.router 构造函数让 go_router 接管整个路由系统。

context 上的路由方法

context.push()context.go()context.pop() 并不是 BuildContext 自带的方法,而是 go_router 提供的扩展方法。只要 import 'package:go_router/go_router.dart',就能在任意 context 上调用。

深度链接

深度链接是指从外部直接打开应用的某个页面,比如:

  • 在浏览器地址栏输入 https://yourapp.com/detail/42,应用直接打开商品详情页
  • 点击一个推送通知,直接跳到对应页面

go_router 内置支持深度链接,只需路径对应上就能自动跳转。Navigator 要实现这个功能需要手写大量代码。

安装

bash
flutter pub add go_router

5 分钟上手

第一步:定义路由表

dart
import 'package:go_router/go_router.dart';

final router = GoRouter(
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => const HomePage(),
    ),
    GoRoute(
      path: '/detail',
      builder: (context, state) => const DetailPage(),
    ),
  ],
);

第二步:挂到 MaterialApp

dart
MaterialApp.router(
  routerConfig: router,
)

第三步:跳转和返回

dart
// 跳转
context.go('/detail');    // 替换式跳转(类似 pushReplacement)
context.push('/detail'); // 压栈跳转(可以返回)⭐ 常用

// 返回
context.pop();

新手提示:大多数情况用 context.push(),这样用户可以点返回键回到上一页。

路径参数(动态路由)

:变量名 定义路径参数,类似 Web 开发中的 /user/:id

dart
GoRoute(
  path: '/user/:id',    // :id 是路径参数
  builder: (context, state) {
    final id = state.pathParameters['id']!;  // 取出参数
    return UserPage(id: id);
  },
),

跳转方式:

dart
context.push('/user/42');  // id 就是 '42'

路径参数适合必须传的数据,如商品 ID、用户 ID。

查询参数

查询参数就是 URL 中 ?key=value 的部分,适合可选的数据:

dart
// 跳转:/search?keyword=flutter&page=1
context.push('/search?keyword=flutter&page=1');

// 获取参数
GoRoute(
  path: '/search',
  builder: (context, state) {
    final keyword = state.uri.queryParameters['keyword'];  // 'flutter'
    final page = int.tryParse(state.uri.queryParameters['page'] ?? '1'); // 1
    return SearchPage(keyword: keyword, page: page);
  },
),

额外参数:extra

如果数据不是字符串(如自定义对象),用 extra 传递:

dart
// 跳转时传
context.push('/detail', extra: Product(id: 42, name: '手机'));

// 接收
GoRoute(
  path: '/detail',
  builder: (context, state) {
    final product = state.extra as Product;  // 取出并转换类型
    return DetailPage(product: product);
  },
),

extra 可以传任意类型的对象,比命名路由的 arguments 好用。

嵌套路由

嵌套路由用于共享布局,比如底部导航栏不变,只切换中间内容:

dart
GoRoute(
  path: '/user',
  builder: (context, state) => const UserShell(),  // 外壳(含底部导航)
  routes: [
    GoRoute(
      path: 'profile',          // 实际路径: /user/profile
      builder: (context, state) => const ProfilePage(),
    ),
    GoRoute(
      path: 'settings',         // 实际路径: /user/settings
      builder: (context, state) => const SettingsPage(),
    ),
  ],
),

错误处理

当用户访问一个不存在的路径时,go_router 会显示默认的错误页面。你也可以自定义:

dart
GoRouter(
  errorBuilder: (context, state) => const NotFoundPage(),
  routes: [...],
)

重定向(路由守卫)

重定向可以在跳转前做判断,最常用的场景是登录检查

dart
GoRouter(
  redirect: (context, state) {
    final isLoggedIn = AuthService.isLoggedIn;
    final isLoginPage = state.matchedLocation == '/login';

    // 未登录且不在登录页 → 跳转到登录页
    if (!isLoggedIn && !isLoginPage) return '/login';
    // 已登录且在登录页 → 跳转到首页
    if (isLoggedIn && isLoginPage) return '/';
    // 不需要重定向
    return null;
  },
  routes: [...],
)

go vs push 的区别

方法行为能否返回适合场景
context.go('/detail')替换栈顶❌ 不能切换底部 Tab、登录后跳首页
context.push('/detail')压入栈顶✅ 能正常的页面跳转

简单记忆:需要能返回就用 push,不需要返回就用 go

go_router 与 Navigator 对比

对比项Navigatorgo_router
风格命令式(手动 push/pop)声明式(定义路由表)
路径参数需手动解析自动解析 :id
Web 浏览器支持有限完整
深度链接手动处理内置支持
路由守卫redirect
传参方式构造函数 / argumentsextra / 路径参数 / 查询参数
官方推荐❌ 旧方案✅ 推荐方案

完整示例

dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

void main() => runApp(const MyApp());

// ---- 定义路由 ----
final router = GoRouter(
  redirect: (context, state) {
    // 简单的登录检查
    final loggedIn = false; // 换成你的登录状态
    final isLogin = state.matchedLocation == '/login';
    if (!loggedIn && !isLogin) return '/login';
    return null;
  },
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => const HomePage(),
    ),
    GoRoute(
      path: '/detail/:id',
      builder: (context, state) {
        final id = state.pathParameters['id']!;
        return DetailPage(id: id);
      },
    ),
    GoRoute(
      path: '/login',
      builder: (context, state) => const LoginPage(),
    ),
  ],
);

// ---- App 入口 ----
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerConfig: router,
    );
  }
}

// ---- 首页 ----
class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('首页')),
      body: Center(
        child: ElevatedButton(
          child: const Text('查看商品 42'),
          onPressed: () => context.push('/detail/42'),
        ),
      ),
    );
  }
}

// ---- 详情页 ----
class DetailPage extends StatelessWidget {
  final String id;

  const DetailPage({super.key, required this.id});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('商品 $id')),
      body: Center(
        child: ElevatedButton(
          child: const Text('返回'),
          onPressed: () => context.pop(),
        ),
      ),
    );
  }
}

// ---- 登录页 ----
class LoginPage extends StatelessWidget {
  const LoginPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('登录')),
      body: const Center(child: Text('请登录')),
    );
  }
}

小结

你想做什么代码
跳转(可返回)context.push('/detail')
跳转(不可返回)context.go('/home')
返回上一页context.pop()
传路径参数context.push('/user/42')state.pathParameters['id']
传查询参数context.push('/search?q=flutter')state.uri.queryParameters['q']
传对象context.push('/detail', extra: obj)state.extra as Obj
登录拦截redirect 回调

基于 Flutter 官方文档整理