Flutter中Redux Thunk Action在FutureBuilder重复加载问题求解
问题原因与解决方案
核心问题
- FutureBuilder无法获取有效数据:
store.dispatch(fetchHomeAction())返回的Future本身不携带接口返回的数据——Thunk仅在内部获取数据后将其dispatch到Redux store,FutureBuilder无法从这个Future中拿到结果。 - 重复触发请求:每次Widget重建时,都会重新执行
store.dispatch(fetchHomeAction()),导致请求重复发起,FutureBuilder始终处于加载循环状态。
解决方案
方案一:修正FutureBuilder的使用方式
1. 调整Thunk Action,让其返回数据或抛出错误
修改Thunk,确保dispatch返回的Future能携带请求结果或错误信息:
ThunkAction<AppState> fetchHomeAction() { return (Store<AppState> store) async { print('thunk action post contact view'); try { dynamic data = await HomeViewRepository().fetchHome(); if (data.result == true) { store.dispatch(HomeResponse( result: data.result, data: data.data, )); return data; // 将数据返回给dispatch的Future } else { throw Exception('请求返回结果异常'); } } catch (e) { debugPrint(e.toString()); rethrow; // 抛出错误,让Future能捕获到 } }; }
2. 在初始化阶段发起请求,避免重复调用
将请求逻辑移到initState中,保存Future对象,防止每次build都重新发起请求:
class _YourPageState extends State<YourPage> { late Future<dynamic> _homeFetchFuture; @override void initState() { super.initState(); // 仅在页面初始化时发起一次请求 _homeFetchFuture = store.dispatch(fetchHomeAction()); } @override Widget build(BuildContext context) { return FutureBuilder<dynamic>( future: _homeFetchFuture, // 使用保存的Future对象 builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) { List<Widget> children; if (snapshot.hasData) { children = <Widget>[ const Icon( Icons.check_circle_outline, color: Colors.green, size: 60, ), Padding( padding: const EdgeInsets.only(top: 16), child: Text('Result: ${snapshot.data}'), ), ]; } else if (snapshot.hasError) { children = <Widget>[ const Icon( Icons.error_outline, color: Colors.red, size: 60, ), Padding( padding: const EdgeInsets.only(top: 16), child: Text('Error: ${snapshot.error}'), ), ]; } else { children = const <Widget>[ SizedBox( width: 60, height: 60, child: CircularProgressIndicator(), ), Padding( padding: EdgeInsets.only(top: 16), child: Text('Awaiting result...'), ), ]; } return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: children, ), ); }, ); } }
方案二:贴合Redux设计的状态驱动方案(推荐)
既然使用Redux,应该通过监听store状态变化来更新UI,而非FutureBuilder。使用StoreConnector连接状态:
StoreConnector<AppState, HomeResponse?>( converter: (store) => store.state.homeResponse, // 假设AppState中存储了homeResponse字段 builder: (context, homeResponse) { // 初始化时发起请求(仅执行一次) if (homeResponse == null) { WidgetsBinding.instance.addPostFrameCallback((_) { store.dispatch(fetchHomeAction()); }); return const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ SizedBox( width: 60, height: 60, child: CircularProgressIndicator(), ), Padding( padding: EdgeInsets.only(top: 16), child: Text('Awaiting result...'), ), ], ), ); } // 根据状态渲染UI if (homeResponse.result == true) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon( Icons.check_circle_outline, color: Colors.green, size: 60, ), Padding( padding: const EdgeInsets.only(top: 16), child: Text('Result: ${homeResponse.data}'), ), ], ), ); } else { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon( Icons.error_outline, color: Colors.red, size: 60, ), const Padding( padding: EdgeInsets.only(top: 16), child: Text('请求失败'), ), ], ), ); } }, )
关键注意事项
- 禁止在
build方法中直接调用带副作用的方法(如store.dispatch),否则会随Widget重建重复执行。 - Redux的核心是状态驱动UI,使用
StoreConnector监听状态变化,比FutureBuilder更契合Redux的设计逻辑,也能从根源避免重复请求问题。
内容的提问来源于stack exchange,提问作者Taukir Ahmmed
相关产品推荐
相关产品推荐

