「这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战」。
本文翻译自
- async-data (异步数据)
- navigator-builder (导航构建器)
- web-history (Web历史)
水平太烂~ 翻到怀疑人生~ 不吝赐教~
[译]Flutter Favorite之路由包go_router - 基础篇 - 掘金 (juejin.cn)
异步数据
有时候,想要异步加载数据。这种情况下,你需要给界面传递参数来展示数据,并让它自己进行查找:
late final _router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomeScreenWithAsync(),
routes: [
GoRoute(
path: 'family/:fid',
builder: (context, state) => FamilyScreenWithAsync(
fid: state.params['fid']!,
),
routes: [
GoRoute(
path: 'person/:pid',
builder: (context, state) => PersonScreenWithAsync(
fid: state.params['fid']!,
pid: state.params['pid']!,
),
),
],
),
],
),
],
);
界面会使用任意内容来查找。例如,下面展示了使用 Repository 模式 和 Flutter FutureBuilder 来加载和展示数据:
class FamilyScreen extends StatefulWidget {
const FamilyScreen({required this.fid, Key? key}) : super(key: key);
final String fid;
@override
State<FamilyScreen> createState() => _FamilyScreenState();
}
class _FamilyScreenState extends State<FamilyScreen> {
Future<Family>? _future;
@override
void initState() {
super.initState();
_fetch();
}
@override
void didUpdateWidget(covariant FamilyScreen oldWidget) {
super.didUpdateWidget(oldWidget);
// refresh cached data
if (oldWidget.fid != widget.fid) _fetch();
}
void _fetch() => _future = App.repo.getFamily(widget.fid);
@override
Widget build(BuildContext context) => FutureBuilder<Family>(
future: _future,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Scaffold(
appBar: AppBar(title: const Text('Loading...')),
body: const Center(child: CircularProgressIndicator()),
);
}
if (snapshot.hasError) {
return Scaffold(
appBar: AppBar(title: const Text('Error')),
body: SnapshotError(snapshot.error!),
);
}
assert(snapshot.hasData);
final family = snapshot.data!;
return Scaffold(
appBar: AppBar(title: Text(family.name)),
body: ListView(
children: [
for (final p in family.people)
ListTile(
title: Text(p.name),
onTap: () => context.go(
'/family/${family.id}/person/${p.id}',
),
),
],
),
);
},
);
}
该代码展示了一个获取数据时的进度指示器和获取失败时的错误。
完整细节参考 异步示例。
导航构建器
有时候,有必要在 Navigator 上、在 MaterialApp/CupertinoApp 下插入组件。例如:插入一个 Provider ,需要访问 App 的 context (上下文) 来获取当前的区域语言和本地化,在导航之外来构建UI或者使用自己的内容 (在本文范围之外)完全替换 Navigator 。
为了这些目的,需要使用 GoRouter 构造器的 navigatorBuilder 参数。这和 MaterialApp 的 builder 参数类似,但是能够访问 MaterialApp 提供的基础设施。
一个放置一些数据Provider组件的示例:
final _router = GoRouter(
routes: ...,
// add a wrapper around the navigator to put loginInfo into the widget tree
navigatorBuilder: (context, state, child) =>
ChangeNotifierProvider<LoginInfo>.value(
value: loginInfo,
builder: (context, _) => child,
),
);
一个使用 navigatorBuilder 的有更趣的示例如下,它会在每个页面添加一个漂浮按钮允许快捷登出:
final _router = GoRouter(
routes: ...,
// add a wrapper around the navigator to:
// - put loginInfo into the widget tree, and to
// - add an overlay to show a logout option
navigatorBuilder: (context, state, child) =>
ChangeNotifierProvider<LoginInfo>.value(
value: loginInfo,
builder: (context, _) =>
loginInfo.loggedIn ? AuthOverlay(child: child) : child;
},
),
);
该示例会检查 navigatorBuilder 中的登录状态:
-
如果用户已登录,
AuthOverlay组件实例会创建,它包装了Navigator,通过child参数传递给navigatorBuilder,并在每个页面提供一个登出按钮。 -
如果用户未登录,则通过
child参数返回Navigator。
AuthOverlay 在 Stack 中展示登出按钮和 Navigator :
class AuthOverlay extends StatelessWidget {
const AuthOverlay({required this.child, Key? key}) : super(key: key);
final Widget child;
@override
Widget build(BuildContext context) => Stack(
children: [
child,
Positioned(
top: 90,
right: 4,
child: ElevatedButton(
onPressed: () {
context.read<LoginInfo>().logout();
context.goNamed('home'); // clear out the `from` query param
},
child: const Icon(Icons.logout),
),
),
],
);
}
动作如下:
Web历史
有时候,导航时不希望浏览器追踪历史记录。这种情况下, go_router 支持 Router.neglect 。
ElevatedButton(
// turn off history tracking in the browser for this navigation
onPressed: () => Router.neglect(context, () => context.go('/page2'))},
...
),
使用 Router.neglect 会用阻止Flutter路由添加此页面到浏览器历史中。如果想要浏览器停止应用中所有的历史记录追踪,需要设置 GoRouter 构造器的 routerNeglect 参数。这会废止所有使用 go_router 导航的历史。
final _router = GoRouter(
// turn off history tracking in the browser for all navigation
routerNeglect: true,
...
);
即使当你指示路由忽略添加导航到浏览器的历史时,深度链接和动态链接也会正常运作,浏览器的地址栏也会通过应用在你导航时更新。它只会影响到浏览器的返回按钮。