You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的类型错误。

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 13:10:15