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

Flutter商品详情页点击加量触发重渲染重复请求接口问题求助

问题根因

  • 你直接把fetchProduct()作为参数传入FutureBuilder的future属性,每次父组件调用setState触发build时,都会重新执行一次fetchProduct()发起接口请求。
  • 数量状态直接在父组件管理,调用setState会触发整个父组件的所有子组件重绘,导致页面全量刷新。

修复方案

第一步:缓存接口请求结果

在State的initState生命周期中仅触发一次接口请求,将返回的Future缓存起来,FutureBuilder直接使用缓存的Future对象,避免每次build都重新发起请求。

第二步:实现局部刷新

使用ValueNotifier+ValueListenableBuilder管理数量状态,更新数量时仅刷新数量显示区域,不会触发整个页面重绘。

修改后核心代码

import 'package:flutter/material.dart';
import 'dart:async';
import 'package:http/http.dart' as http;
import 'dart:convert';

class ProductDetailScreen extends StatelessWidget{
  @override
  Widget build(BuildContext context)
  {
    return  MaterialApp(
      title: "Product #1",
      home:Scaffold(
        appBar: AppBar( title: const Text('Product #1', style: TextStyle(color: Colors.deepOrange)),
            backgroundColor: Colors.white,
            iconTheme: const IconThemeData(color: Colors.deepOrange),
            actions: <Widget>[
              IconButton(onPressed: (){
                ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(content: Text('This is a snackbar')));
              },icon: const Icon(Icons.shopping_cart),color: Colors.deepOrange,)
            ],
            leading: BackButton(color: Colors.deepOrange,
                onPressed: () => Navigator.of(context).pop())
        ),
        body: SafeArea(child: Center(
            child: ProductsDetailsScreen()
        ),
        ),
      ),
    );
  }
}

class ProductsDetailsScreen extends StatefulWidget{
  const ProductsDetailsScreen({Key? key}) : super(key: key);
  @override
  _ProductsDetailScreenState createState() => _ProductsDetailScreenState();
}

class _ProductsDetailScreenState extends State<ProductsDetailsScreen>
{
  // 缓存请求结果的Future,仅初始化一次
  Future<List>? _productFuture;
  // 用ValueNotifier管理数量状态,不需要setState更新
  final ValueNotifier<int> _qtyNotifier = ValueNotifier(0);

  @override
  void initState() {
    super.initState();
    // 页面初始化时仅请求一次接口
    _productFuture = fetchProduct();
  }

  Future<List> fetchProduct() async{
    final response = await http.get(Uri.parse('https://www.thecocktaildb.com/api/json/v1/1/random.php'));
    final product = json.decode(response.body);
    List  prod = product['drinks'];
    List<Prod> prods = [];
    for(var p in prod){
      Prod product = Prod(idDrink: p["idDrink"],strDrink: p['strDrink'],strDrinkThumb: p['strDrinkThumb'] );
      prods.add(product);
    }
    return prods;
  }
  @override
  Widget build(BuildContext context)
  {
    return Container(
        child:FutureBuilder(
            // 使用缓存的Future,不会每次build都重新请求
            future  : _productFuture,
            builder: (BuildContext context, AsyncSnapshot snapshot){
              if(snapshot.data == null)
              {
                return Container(
                    child:const Text("Loading...")
                );
              }else {
                return Column(
                    children: List<Widget>.generate(snapshot.data.length,(int index) {
                      return Column(
                                crossAxisAlignment: CrossAxisAlignment.center,
                                mainAxisAlignment: MainAxisAlignment.center,
                                children :<Widget>[
                                  Image.network(snapshot.data[index].strDrinkThumb,
                                    fit: BoxFit.contain,
                                  ),
                                  Text(snapshot.data[index].strDrink.toUpperCase(),
                                    style: const TextStyle(color: Colors.black,fontFamily: "Verdana",
                                      fontSize: 20,
                                    ),
                                  ),
                                  const Text('49.00',
                                    style: TextStyle(color: Colors.deepOrange,fontFamily: "Verdana",
                                      fontSize: 20,
                                    ),
                                  ),
                                  Row(
                                      mainAxisAlignment: MainAxisAlignment.center,
                                      children  :<Widget>[
                                        Expanded(
                                            flex:2,
                                            child:  ElevatedButton(
                                              onPressed: (){
                                                if(_qtyNotifier.value>0) _qtyNotifier.value--;
                                              },
                                              child: const Text('-'),
                                              style: ElevatedButton.styleFrom(primary:Colors.deepOrange,
                                              ),)
                                        ),
                                        Expanded(
                                          flex:6,
                                          // 仅监听数量变化,局部刷新数量显示
                                          child: ValueListenableBuilder<int>(
                                            valueListenable: _qtyNotifier,
                                            builder: (context, qty, child) {
                                              return Container(alignment: Alignment.center,
                                                  child:Text('$qty'));
                                            },
                                          ),
                                        ),
                                        Expanded(
                                            flex:2,
                                            child: ElevatedButton(
                                                onPressed: (){
                                                  _qtyNotifier.value++;
                                                },
                                                child: const Text('+'),
                                                style: ElevatedButton.styleFrom(primary:Colors.deepOrange))
                                        ),
                                      ]
                                  ),
                                  Column(
                                      crossAxisAlignment: CrossAxisAlignment.stretch,
                                      children:<Widget> [
                                        ElevatedButton.icon(
                                          icon: const Icon(Icons.shopping_basket_rounded),
                                          onPressed: (){
                                            // 取当前数量直接读取_qtyNotifier.value即可
                                          },
                                          label: const Text('Add to trolley'),
                                          style: ElevatedButton.styleFrom(primary:Colors.deepOrange,
                                          ),
                                        )]
                                  )
                                ],
                              );
                    }
                    )
                );
              }}
        )
    );
  }
}

// 补上你代码中缺失的Prod类
class Prod {
  final String idDrink;
  final String strDrink;
  final String strDrinkThumb;
  Prod({required this.idDrink, required this.strDrink, required this.strDrinkThumb});
}

修改后接口只会在页面初始化时请求一次,更新数量时仅数量区域会刷新,不会触发全页面重绘也不会重复拉取接口。

内容的提问来源于stack exchange,提问作者JS_development_

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 00:27:02