You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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),
    );
  }
}

问题原因

  1. FutureBuilder的future重复执行:FutureBuilder的future参数直接绑定future.call(id: id),每次ABCScreen重建时都会重新调用该方法,触发新的API请求。
  2. GoRouter push的路由栈特性:context.push()是将新页面添加到路由栈,GoRouter内部逻辑会导致当前页面的父组件(ABCScreen)重建;而context.go()是替换路由栈,不会触发这种层级的重建。
  3. 不必要的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 20:52:07