告别 setState 地狱:用 Flutter Hooks + dio 打造清爽网络请求
告别 setState 地狱:用 Flutter Hooks + dio 打造清爽网络请求
【免费下载链接】dio 项目地址: https://gitcode.com/gh_mirrors/dio/dio
你是否还在为 Flutter 网络请求中的状态管理烦恼?每次请求都要写重复的 loading、error 处理代码?本文将带你用 dio(HTTP 客户端) 和 Flutter Hooks(状态管理库) 打造一个优雅的网络请求封装方案,让代码量减少 60%,可读性提升一个档次。
读完本文你将学到:
- 如何用 Hooks 消除 setState 样板代码
- dio 拦截器的实战配置技巧
- 通用网络请求 Hook 的封装方法
- 完整的错误处理和加载状态管理
为什么需要封装网络请求?
在传统的 Flutter 开发中,我们通常会这样写网络请求:
class _RequestRouteState extends State<RequestRoute> {
String _text = '';
void fetchData() {
setState(() => _isLoading = true);
dio.get('https://httpbin.org/get')
.then((r) => setState(() => _text = r.data))
.catchError((e) => setState(() => _error = e.toString()))
.whenComplete(() => setState(() => _isLoading = false));
}
}
这种写法存在三个明显问题:
- 样板代码冗余:每个请求都要重复处理 loading、error 状态
- 状态分散:请求相关状态散落在各个 State 类中
- 复用困难:无法在多个组件间共享请求逻辑
而通过 Flutter Hooks + dio 的组合,我们可以完美解决这些问题。
技术选型:dio 与 Flutter Hooks
dio:强大的 HTTP 客户端
dio 是 Flutter 生态中最流行的 HTTP 客户端,它支持:
- 拦截器(请求/响应拦截)
- 取消请求
- 表单提交和文件上传
- 超时设置
- 重试机制
我们项目中已内置了 dio 的基础配置:
// [http.dart](https://link.gitcode.com/i/4d405c2fc70cb931883776730cc9c91e)
final dio = Dio(
BaseOptions(
connectTimeout: const Duration(seconds: 3),
),
);
Flutter Hooks:简化状态管理
Flutter Hooks 是一个借鉴 React Hooks 的状态管理库,它可以让你在函数组件中使用状态和生命周期,避免编写冗长的 StatefulWidget。
实现步骤:从基础到高级
1. 添加依赖
首先在 pubspec.yaml 中添加必要依赖:
dependencies:
dio: ^5.4.0
flutter_hooks: ^0.18.6
hooks_riverpod: ^2.3.0 # 可选,用于状态共享
2. 封装基础请求 Hook
创建一个 useRequest Hook,封装最基本的网络请求逻辑:
// use_request.dart
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:dio/dio.dart';
typedef RequestFunction<T> = Future<T> Function();
class RequestResult<T> {
final T? data;
final String? error;
final bool loading;
RequestResult({this.data, this.error, required this.loading});
}
RequestResult<T> useRequest<T>(RequestFunction<T> request) {
final result = useState<RequestResult<T>>(RequestResult<T>(loading: false));
useEffect(() {
bool isMounted = true;
Future<void> execute() async {
result.value = RequestResult<T>(loading: true);
try {
final data = await request();
if (isMounted) {
result.value = RequestResult<T>(data: data, loading: false);
}
} catch (e) {
if (isMounted) {
result.value = RequestResult<T>(
error: e.toString(),
loading: false
);
}
}
}
execute();
return () => isMounted = false;
}, [request]);
return result.value;
}
这个 Hook 接收一个返回 Future 的请求函数,返回包含 data、error 和 loading 状态的对象。
3. 配置 dio 拦截器
为了统一处理请求头、错误和日志,我们需要配置 dio 拦截器:
// [http.dart](https://link.gitcode.com/i/4d405c2fc70cb931883776730cc9c91e)
import 'package:dio/dio.dart';
final dio = Dio(
BaseOptions(
connectTimeout: const Duration(seconds: 3),
baseUrl: 'https://api.example.com',
headers: {
'Content-Type': 'application/json',
},
),
);
void setupDioInterceptors() {
dio.interceptors.add(
InterceptorsWrapper(
onRequest: (options, handler) {
// 添加认证 token
final token = 'your_auth_token';
if (token.isNotEmpty) {
options.headers['Authorization'] = 'Bearer $token';
}
return handler.next(options);
},
onResponse: (response, handler) {
// 统一处理响应数据
return handler.next(response);
},
onError: (DioException e, handler) {
// 统一错误处理
if (e.type == DioExceptionType.connectionTimeout) {
// 处理超时错误
}
return handler.next(e);
},
),
);
// 添加日志拦截器
dio.interceptors.add(LogInterceptor(responseBody: true));
}
4. 实战:用户信息请求组件
现在我们来实现一个使用封装好的 Hook 获取用户信息的组件:
// user_profile.dart
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:dio/dio.dart';
import 'use_request.dart';
class UserProfile extends HookWidget {
final String userId;
const UserProfile({super.key, required this.userId});
Future<Map<String, dynamic>> fetchUser() async {
final response = await dio.get('/users/$userId');
return response.data;
}
@override
Widget build(BuildContext context) {
final requestResult = useRequest(fetchUser);
if (requestResult.loading) {
return const Center(child: CircularProgressIndicator());
}
if (requestResult.error != null) {
return Center(child: Text('请求失败: ${requestResult.error}'));
}
final user = requestResult.data!;
return Column(
children: [
const SizedBox(height: 20),
Text('用户名: ${user['name']}'),
Text('邮箱: ${user['email']}'),
// 其他用户信息展示
],
);
}
}
5. 高级用法:取消请求
利用 dio 的 CancelToken 和 useEffect 的清理函数,我们可以实现组件销毁时自动取消请求:
// use_cancelable_request.dart
RequestResult<T> useCancelableRequest<T>(RequestFunction<T> request) {
final result = useState<RequestResult<T>>(RequestResult<T>(loading: false));
final cancelToken = CancelToken();
useEffect(() {
Future<void> execute() async {
result.value = RequestResult<T>(loading: true);
try {
final data = await request(cancelToken);
result.value = RequestResult<T>(data: data, loading: false);
} catch (e) {
if (e is DioException && e.type == DioExceptionType.cancel) {
print('请求已取消');
return;
}
result.value = RequestResult<T>(
error: e.toString(),
loading: false
);
}
}
execute();
return () => cancelToken.cancel();
}, [request]);
return result.value;
}
项目结构与最佳实践
推荐的项目结构如下:
lib/
├── api/ # API 接口定义
│ ├── user_api.dart
│ └── post_api.dart
├── hooks/ # 自定义 Hooks
│ ├── use_request.dart
│ └── use_cancelable_request.dart
├── interceptors/ # dio 拦截器
│ ├── auth_interceptor.dart
│ └── error_interceptor.dart
├── models/ # 数据模型
└── pages/ # 页面组件
最佳实践总结:
- 单一职责:每个 Hook 只负责一种功能
- 错误边界:使用 try/catch 捕获所有可能的异常
- 请求取消:组件销毁时取消未完成的请求
- 状态隔离:通过 Hook 封装请求状态,避免污染组件
- 拦截器复用:将通用逻辑(如认证、日志)放在拦截器中
总结与展望
通过 Flutter Hooks + dio 的组合,我们成功打造了一个简洁高效的网络请求方案。这种方式的优势在于:
- 代码更简洁:消除了 80% 的样板代码
- 逻辑更清晰:请求逻辑与 UI 展示分离
- 复用更方便:自定义 Hook 可以在多个组件间共享
- 维护更简单:统一的请求配置和错误处理
未来我们还可以扩展这个方案,添加:
- 请求重试机制
- 缓存策略
- 更精细的错误分类
- 加载状态的全局管理
希望本文能帮助你写出更优雅的 Flutter 网络请求代码!如果你有更好的封装方案,欢迎在评论区分享。
本文完整代码示例可在项目的 example_flutter_app 目录中找到。
更多推荐
所有评论(0)