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_
相关产品推荐
相关产品推荐

