Flutter GoRouter context.push()异常:组件重建重复调用API求助
Flutter GoRouter跳转触发父组件重建问题解决
问题场景
开发Flutter项目时使用GoRouter遇到异常行为:
- 从XYZSreen调用
context.push('/path')跳转至QWEScreen时,父组件ABCScreen会重建,导致额外的API调用 - 改用
Navigator.push()或context.go()跳转则无此问题,希望继续使用GoRouter的push方法解决该问题
相关代码
ABCScreen组件
class ABCScreen extends StatelessWidget { final String id; const ABCScreen({super.key, required this.id}); @override Widget build(BuildContext context) { return Consumer<ABCState>(builder: (context, future, child) { return FutureBuilder( future: future.call(id: id), builder: ((context, snapshot) { if (snapshot.data != null && snapshot.connectionState == ConnectionState.done) { return XYZScreen(model: snapshot.data!); } else { return const ShimmerScreen(); } }), ); }); } }
ABCState状态类
class ABCState extends ChangeNotifierProvider { Future<Model?> call({required String id}) async { Model? data = await ABCServices().fetchData(id: id); // REST API call if (data != null) { return data; } return null; } }
XYZScreen组件
class XYZScreen extends StatelessWidget { const XYZScreen({ super.key, required this.model, }); final Model model ; @override Widget build(BuildContext context) { return InkWell( onTap: () { if (context.mounted) { context.push('/path'); } }, child: SomeContainer(model: model), ); } }
问题原因
- FutureBuilder的future重复执行:
FutureBuilder的future参数直接绑定future.call(id: id),每次ABCScreen重建时都会重新调用该方法,触发新的API请求。 - GoRouter push的路由栈特性:
context.push()是将新页面添加到路由栈,GoRouter内部逻辑会导致当前页面的父组件(ABCScreen)重建;而context.go()是替换路由栈,不会触发这种层级的重建。 - 不必要的Consumer监听:代码中使用
Consumer<ABCState>,但ABCState并未调用notifyListeners(),GoRouter导航时的上下文变化会间接触发Consumer的builder重新执行,进而导致FutureBuilder重建。
解决方案
方案1:缓存Future避免重复调用
将ABCScreen改为StatefulWidget,在initState中仅创建一次Future,避免每次build都重新发起API请求:
class ABCScreen extends StatefulWidget { final String id; const ABCScreen({super.key, required this.id}); @override State<ABCScreen> createState() => _ABCScreenState(); } class _ABCScreenState extends State<ABCScreen> { late Future<Model?> _fetchFuture; @override void initState() { super.initState(); // 仅初始化时获取一次Future,避免重复调用API _fetchFuture = Provider.of<ABCState>(context, listen: false).call(id: widget.id); } @override Widget build(BuildContext context) { return FutureBuilder( future: _fetchFuture, builder: ((context, snapshot) { if (snapshot.data != null && snapshot.connectionState == ConnectionState.done) { return XYZScreen(model: snapshot.data!); } else { return const ShimmerScreen(); } }), ); } }
这里移除了不必要的Consumer,因为没有监听状态变化,直接通过Provider.of获取实例即可。
方案2:规范GoRouter路由调用
确保路由配置中/path是独立的路由项,可尝试使用命名路由跳转,避免路径字符串可能带来的上下文问题:
// 假设路由配置中给/path命名为'qwepage' context.pushNamed('qwepage');
方案3:在State中缓存API结果(可选)
如果后续允许改动,可在ABCState中缓存请求结果,彻底避免重复API调用:
class ABCState extends ChangeNotifier { Model? _cachedData; Future<Model?> call({required String id}) async { // 缓存存在则直接返回 if (_cachedData != null) return _cachedData; Model? data = await ABCServices().fetchData(id: id); if (data != null) { _cachedData = data; } return data; } }
内容的提问来源于stack exchange,提问作者Harshit Singh
相关产品推荐
相关产品推荐

