Flutter3+Dio通用HTTP请求实现的性能、写法及泛型疑问
Flutter Dio通用请求封装疑问解答
你的封装代码如下:
Future<T> call<T>( HttpEndpoint endpoint, T Function(Map<String, dynamic>) fromJson, ) async { final response = await dio.request( endpoint.path, data: endpoint.body, options: Options(method: endpoint.method), ); return fromJson(response.data); } class HttpEndpoint { final String path; final String method; final Map<String, dynamic> body; HttpEndpoint({ required this.path, required this.method, this.body = const {}, }); }
1. 该写法的性能问题与隐患
- 性能无明显影响:泛型和函数参数都是Dart编译期处理的逻辑,不会带来运行时额外开销。
- 存在的隐患:
- 无错误处理:Dio请求会抛出网络错误、状态码异常等,但当前代码未捕获,上层调用若未加
try/catch会直接崩溃。 - 请求配置受限:
HttpEndpoint仅包含path、method、body,无法自定义header、超时时间、query参数等场景。 - 响应无校验:未判断
response.data是否为Map<String, dynamic>,若后端返回非Map格式(比如数组、字符串),会直接触发fromJson的类型错误。
- 无错误处理:Dio请求会抛出网络错误、状态码异常等,但当前代码未捕获,上层调用若未加
2. 已封装HttpEndpoint前提下的行业流行写法
主流优化方向集中在增强类型安全、统一错误处理、扩展配置灵活性,示例如下:
优化后的核心实现
// 自定义业务异常,统一错误格式 class ApiException implements Exception { final int code; final String message; ApiException(this.code, this.message); } // 扩展HttpEndpoint,绑定实体类与转换逻辑 class HttpEndpoint<T> { final String path; final String method; final Map<String, dynamic> body; final Map<String, String>? headers; final Map<String, dynamic>? queryParams; final Duration? timeout; // 直接把fromJson绑定到Endpoint,上层无需重复传递 final T Function(Map<String, dynamic>) fromJson; HttpEndpoint({ required this.path, required this.method, this.body = const {}, this.headers, this.queryParams, this.timeout, required this.fromJson, }); } // 通用请求方法,内置错误处理与校验 Future<T> call<T>(HttpEndpoint<T> endpoint) async { try { final response = await dio.request( endpoint.path, data: endpoint.body, queryParameters: endpoint.queryParams, options: Options( method: endpoint.method, headers: endpoint.headers, sendTimeout: endpoint.timeout ?? const Duration(seconds: 15), ), ); // 校验响应数据格式 if (response.data is! Map<String, dynamic>) { throw ApiException(-1, "响应数据格式不符合预期"); } return endpoint.fromJson(response.data); } on DioException catch (e) { // 转换Dio原生异常为业务异常 final statusCode = e.response?.statusCode ?? -1; final errorMsg = e.response?.data?["msg"] ?? e.message ?? "网络请求失败"; throw ApiException(statusCode, errorMsg); } catch (e) { throw ApiException(-2, "未知错误"); } }
其他流行思路
- 用枚举定义所有API:每个枚举值对应一个接口,内置path、method、fromJson等信息,彻底避免重复配置。
- 结合
json_serializable:封装默认的fromJson逻辑,减少手动编写转换函数的工作量。 - 加入泛型约束:比如限制
T extends JsonSerializable,进一步增强类型安全。
3. fromJson参数前的泛型T的作用
这个T绝对不能删除,核心作用是绑定函数返回类型与整个call方法的返回类型:
- 强制
fromJson必须返回T类型的对象,避免上层传递错误的转换函数(比如把User.fromJson传给需要Order类型的请求)。 - 帮助编译器自动类型推导:上层调用时无需显式指定
T,比如call(userEndpoint)就能自动推断返回User类型。 - 若删除这个T,
fromJson的返回类型会变成dynamic,call方法的泛型T就失去了类型约束,会导致编译期无法检测类型错误,只能在运行时崩溃。
内容的提问来源于stack exchange,提问作者AllinProgram
相关产品推荐
相关产品推荐

