Skip to content

速查 07 · GoRouter ​

按问题索引。Ctrl+F 页内搜索。 系统学习见 13 · 路由与导航。

本项目版本:GoRouter 14。

目录 ​


最小配置 ​

dart
final router = GoRouter(
  initialLocation: '/',
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => const HomePage(),
      routes: [                                    // 子路由
        GoRoute(
          path: 'detail/:id',                      // 注意**不带前导斜杠**
          builder: (context, state) =>
              DetailPage(id: state.pathParameters['id']!),
        ),
      ],
    ),
  ],
);

// MaterialApp 换成 router 构造
MaterialApp.router(routerConfig: router)

📌 子路由的 path 不要写 / 开头,否则会变成根路径。


go / push / replace / pop 怎么选 ​

方法行为场景
context.go('/x')替换整个路由栈底部导航切换、登录后进入主页
context.push('/x')压栈(可返回)从列表进详情
context.replace('/x')替换当前页(不新增)表单提交后跳结果页
context.pop()出栈返回;可带返回值 pop(true)
context.canPop()判断能否出栈避免"栈里只剩一页时 pop 导致黑屏"
dart
// 带返回值
final ok = await context.push<bool>('/edit');
if (ok == true) ref.invalidate(listProvider);

// 安全的返回
if (context.canPop()) context.pop(); else context.go('/');

命名路由与参数 ​

dart
GoRoute(
  path: '/user/:id',
  name: 'user',
  builder: (context, state) => UserPage(id: state.pathParameters['id']!),
)

// 跳转:三种参数各有用处
context.goNamed('user', pathParameters: {'id': '123'});
context.goNamed('user', pathParameters: {'id': '123'}, queryParameters: {'tab': 'info'});
context.goNamed('user', pathParameters: {'id': '123'}, extra: userObject);  // 传对象
参数出现在 URL能刷新保持适合
pathParameters✅ /user/123✅必需的资源 id
queryParameters✅ ?tab=info✅筛选、分页、Tab
extra❌ 内存传递❌ 刷新即丢临时对象(别放关键数据)

⚠️ extra 刷新后丢失:需要刷新保持的一律用 pathParameters / queryParameters。


嵌套路由与底部导航 ​

dart
// ① ShellRoute:共享一个外壳(如顶部导航)
ShellRoute(
  builder: (context, state, child) => Scaffold(body: child),
  routes: [ /* 子路由 */ ],
)

// ② 底部导航且**保持各 Tab 状态** → 用 indexedStack
StatefulShellRoute.indexedStack(
  builder: (context, state, navigationShell) =>
      ScaffoldWithNavBar(navigationShell: navigationShell),
  branches: [
    StatefulShellBranch(routes: [GoRoute(path: '/home',  builder: ...)]),
    StatefulShellBranch(routes: [GoRoute(path: '/mine',  builder: ...)]),
  ],
)
// 切换 Tab
navigationShell.goBranch(index);

💡 StatefulShellRoute.indexedStack 是 GoRouter 10+ 的方案, 它让每个分支有独立 Navigator,Tab 切换不丢滚动位置与状态。


重定向与登录守卫 ​

dart
final router = GoRouter(
  redirect: (context, state) {
    final loggedIn = authState.value ?? false;
    final goingToLogin = state.matchedLocation == '/login';

    if (!loggedIn && !goingToLogin) return '/login?from=${state.uri}';   // 拦截
    if (loggedIn && goingToLogin) return '/';                            // 已登录别回登录页
    return null;                                                          // 放行
  },
  // ✅ 让登录状态变化时**重新触发** redirect
  refreshListenable: authNotifier,
  routes: [...],
);

要点:

要点说明
返回 null = 放行返回路径字符串 = 重定向
防止无限重定向必须判断"当前是否已是要去的页面"
refreshListenable状态变了才重新 redirect,否则登录成功后不会自动跳
单路由守卫在 GoRoute 上单独写 redirect

读取参数 ​

dart
// ① 在 builder 里(最常用,类型安全)
GoRoute(
  path: '/user/:id',
  builder: (context, state) {
    final id = state.pathParameters['id']!;
    final tab = state.uri.queryParameters['tab'];
    return UserPage(id: id, tab: tab);
  },
)

// ② 在任意 Widget 里
final state = GoRouterState.of(context);
final id = state.pathParameters['id'];
final full = state.uri.toString();

错误页与兜底 ​

dart
GoRouter(
  errorBuilder: (context, state) => NotFoundPage(uri: state.uri),   // 404 兜底
  routes: [...],
)

搭配 Nginx / 静态托管的 fallback 到 index.html(Web 端刷新 404 的根因, 详见 A15 · 路由与数据加载)。


常见报错速查 ​

报错 / 现象原因解法
GoException: no routes for location路径没在 routes 里注册检查拼写;或加 errorBuilder 兜底
子路由写成 /detail 后变成根路径子路由 path 带了前导 /改回 detail
登录后没有自动跳转少了 refreshListenable把登录状态(Notifier / ValueNotifier)传进去
重定向死循环redirect 没判断"已在目标页"加 if (goingToLogin) return null 之类判断
刷新页面后 extra 变 nullextra 只存内存关键数据改放 pathParameters / 本地存储
Web 端刷新子页面 404服务端没配 fallback配 try_files $uri /index.html 或 _redirects
底部 Tab 切换后状态丢失用了普通 ShellRoute 或直接 go改用 StatefulShellRoute.indexedStack
There are multiple heroes / 转场异常同名 Hero 或 Navigator 冲突给 Hero 加 tag;确认嵌套 Navigator 层级

相关文档 ​