告别重复网络代码:Flutter Hooks+Dio自定义Hook封装指南
你是否还在为Flutter项目中重复的网络请求代码感到困扰?每次发起请求都要处理加载状态、错误捕获和数据解析,不仅冗余还容易出错。本文将带你通过Flutter Hooks与Dio的结合,打造一个优雅的网络请求解决方案,让你的代码更简洁、更可维护。读完本文,你将掌握自定义Hook封装Dio的完整流程,学会如何在实际项目中应用这一最佳实践。
技术选型:为什么选择Dio和Flutter Hooks?
Dio作为Flutter生态中最强大的HTTP客户端,提供了拦截器、FormData、文件上传下载等丰富功能,其核心代码位于dio/lib/dio.dart。而Flutter Hooks则是一个轻量级的状态管理库,通过自定义Hook可以将组件逻辑抽象复用,两者结合能极大提升开发效率。
核心优势对比
| 传统方式 | Hooks封装方式 |
|---|---|
| 重复State声明 | 单一Hook调用 |
| 生命周期复杂 | useEffect统一管理 |
| 错误处理分散 | 集中式异常捕获 |
| 代码冗余度高 | 逻辑抽象复用 |
从零开始:自定义Hook封装步骤
1. 基础架构搭建
首先创建一个网络请求基础类,封装Dio实例和拦截器配置:
// [example_flutter_app/lib/http.dart](https://link.gitcode.com/i/f2bec7539b5a6d2bf4e0bb384e1dc824)
import 'package:dio/dio.dart';
class HttpUtil {
static final Dio _dio = Dio(BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: Duration(seconds: 5),
receiveTimeout: Duration(seconds: 3),
));
static Dio get instance => _dio;
static void init() {
_dio.interceptors.add(LogInterceptor(responseBody: true));
// 可添加CookieManager等插件 [plugins/cookie_manager/](https://link.gitcode.com/i/dbd751c8825bf535b01dbb2ad8edd8ca)
}
}
2. 网络请求Hook实现
创建useRequest自定义Hook,统一管理请求状态:
// [example_flutter_app/lib/routes/request.dart](https://link.gitcode.com/i/c70141830aae454eba8a55cbc10d8f4a)
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:dio/dio.dart';
typedef RequestCallback<T> = Future<T> Function();
class RequestResult<T> {
final T? data;
final bool loading;
final DioException? error;
RequestResult({this.data, required this.loading, this.error});
}
RequestResult<T> useRequest<T>(RequestCallback<T> request) {
final data = useState<T?>(null);
final loading = useState<bool>(false);
final error = useState<DioException?>(null);
final cancelToken = CancelToken();
useEffect(() {
return () => cancelToken.cancel('Request canceled');
}, []);
Future<void> execute() async {
loading.value = true;
try {
final result = await request();
data.value = result;
error.value = null;
} on DioException catch (e) {
error.value = e;
} finally {
loading.value = false;
}
}
return RequestResult(
data: data.value,
loading: loading.value,
error: error.value,
);
}
3. 高级功能集成
添加请求取消和进度监听功能,利用Dio的CancelToken和Stream:
// 添加文件上传进度监听
void uploadFile(String path) async {
await HttpUtil.instance.post(
'/upload',
data: FormData.fromMap({
'file': await MultipartFile.fromFile(path),
}),
onSendProgress: (int sent, int total) {
double progress = sent / total;
print('上传进度: ${(progress * 100).toStringAsFixed(0)}%');
},
);
}
实际应用:用户登录场景示例
// [example_flutter_app/lib/main.dart](https://link.gitcode.com/i/c34ea1e750390b00d2c96642ab0ad7c3)
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:dio/dio.dart';
import 'http.dart';
import 'routes/request.dart';
class LoginPage extends HookWidget {
@override
Widget build(BuildContext context) {
final username = useState('');
final password = useState('');
final request = useRequest(() async {
return await HttpUtil.instance.post(
'/login',
data: {'username': username.value, 'password': password.value},
);
});
return Scaffold(
body: Column(
children: [
TextField(
onChanged: (v) => username.value = v,
decoration: InputDecoration(labelText: '用户名'),
),
TextField(
onChanged: (v) => password.value = v,
obscureText: true,
decoration: InputDecoration(labelText: '密码'),
),
ElevatedButton(
onPressed: request.loading ? null : () => request.execute(),
child: request.loading ? CircularProgressIndicator() : Text('登录'),
),
if (request.error != null)
Text('登录失败: ${request.error!.message}',
style: TextStyle(color: Colors.red)),
],
),
);
}
}
最佳实践与避坑指南
1. 拦截器最佳配置
推荐使用拦截器处理通用逻辑,如Token刷新:
// [dio/lib/src/interceptor.dart](https://link.gitcode.com/i/8f0c447e9076460dbab843e4e56403a2)
class TokenInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
options.headers['Authorization'] = 'Bearer token';
super.onRequest(options, handler);
}
@override
Future onError(DioException err, ErrorInterceptorHandler handler) async {
if (err.response?.statusCode == 401) {
// 实现Token刷新逻辑
}
super.onError(err, handler);
}
}
2. 内存管理注意事项
- 使用CancelToken及时取消无用请求,避免内存泄漏
- 通过useEffect清理函数释放资源
- 避免在Hook中直接创建Dio实例,推荐使用单例模式
项目资源与扩展学习
- 官方文档:dio/README-ZH.md
- 测试用例:dio/test/
- 插件扩展:plugins/
- 完整示例:example_flutter_app/
通过Flutter Hooks与Dio的结合,我们成功将网络请求逻辑抽象为可复用的自定义Hook,大幅减少了重复代码。这种模式不仅适用于网络请求,还可推广到数据库操作、权限检查等场景。立即尝试在你的项目中应用这一实践,提升代码质量和开发效率!
如果觉得本文对你有帮助,别忘了点赞、收藏、关注三连,下期我们将探讨Dio的高级拦截器设计模式!
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



