告别 setState 地狱:用 Flutter Hooks + dio 打造清爽网络请求

【免费下载链接】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));
  }
}

这种写法存在三个明显问题:

  1. 样板代码冗余:每个请求都要重复处理 loading、error 状态
  2. 状态分散:请求相关状态散落在各个 State 类中
  3. 复用困难:无法在多个组件间共享请求逻辑

而通过 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/             # 页面组件

最佳实践总结:

  1. 单一职责:每个 Hook 只负责一种功能
  2. 错误边界:使用 try/catch 捕获所有可能的异常
  3. 请求取消:组件销毁时取消未完成的请求
  4. 状态隔离:通过 Hook 封装请求状态,避免污染组件
  5. 拦截器复用:将通用逻辑(如认证、日志)放在拦截器中

总结与展望

通过 Flutter Hooks + dio 的组合,我们成功打造了一个简洁高效的网络请求方案。这种方式的优势在于:

  • 代码更简洁:消除了 80% 的样板代码
  • 逻辑更清晰:请求逻辑与 UI 展示分离
  • 复用更方便:自定义 Hook 可以在多个组件间共享
  • 维护更简单:统一的请求配置和错误处理

未来我们还可以扩展这个方案,添加:

  • 请求重试机制
  • 缓存策略
  • 更精细的错误分类
  • 加载状态的全局管理

希望本文能帮助你写出更优雅的 Flutter 网络请求代码!如果你有更好的封装方案,欢迎在评论区分享。

本文完整代码示例可在项目的 example_flutter_app 目录中找到。

【免费下载链接】dio 【免费下载链接】dio 项目地址: https://gitcode.com/gh_mirrors/dio/dio

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐