速查 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 变 null | extra 只存内存 | 关键数据改放 pathParameters / 本地存储 |
| Web 端刷新子页面 404 | 服务端没配 fallback | 配 try_files $uri /index.html 或 _redirects |
| 底部 Tab 切换后状态丢失 | 用了普通 ShellRoute 或直接 go | 改用 StatefulShellRoute.indexedStack |
There are multiple heroes / 转场异常 | 同名 Hero 或 Navigator 冲突 | 给 Hero 加 tag;确认嵌套 Navigator 层级 |
相关文档
- 13 · 路由与导航 —— 本项目 GoRouter 配置与实战
- 14 · 国际化 —— 多语言与路由的配合
- A15 · 路由与数据加载 —— 前端视角(history fallback、请求竞态)
- 速查 01 · Flutter —— 导航路由基础