Flutter Dio、GetX 深入解析与状态管理对比

0 阅读13分钟

目录

  - 1.1 Dio 是什么

  - 1.2 核心架构

  - 1.3 底层原理

  - 1.4 拦截器机制

  - 1.5 请求流程全链路

  - 1.6 高级特性原理

  - 2.1 GetX 是什么

  - 2.2 三大核心模块

  - 2.3 响应式状态管理原理

  - 2.4 依赖注入原理

  - 2.5 路由管理原理

  - 2.6 GetX 的生命周期管理

  - 3.1 setState

  - 3.2 InheritedWidget / Provider

  - 3.3 Riverpod

  - 3.4 BLoC / Cubit

  - 3.5 GetX

  - 3.6 MobX

  - 3.7 综合对比表

  - 3.8 如何选择


一、Dio 深入解析

1.1 Dio 是什么

Dio 是 Flutter/Dart 生态中最流行的 HTTP 客户端库,由国人开发者 wendux 创建。它是对 Dart 原生 HttpClient 的高级封装,提供了丰富的功能:

  • RESTful API 支持(GET、POST、PUT、DELETE 等)

  • 拦截器(Interceptors)

  • 全局配置(BaseOptions)

  • FormData / 文件上传下载

  • 请求取消(CancelToken)

  • 超时配置

  • 自动 JSON 序列化/反序列化

  • 适配器模式(可切换底层实现)

一句话理解:Dio 就像一个"HTTP 请求的流水线工厂",原始的请求从一端进去,经过一系列加工(拦截器、转换器、适配器),最终得到处理好的响应。

1.2 核心架构


┌──────────────────────────────────────────────────────┐

│                     Dio 实例                          │

│                                                      │

│  ┌──────────┐  ┌──────────────┐  ┌───────────────┐  │

│  │ Options  │  │ Interceptors │  │  Transformer  │  │

│  │ (配置中心) │  │  (拦截器链)   │  │  (数据转换器)  │  │

│  └──────────┘  └──────────────┘  └───────────────┘  │

│                                                      │

│  ┌──────────────────────────────────────────────┐    │

│  │            HttpClientAdapter                  │    │

│  │          (底层适配器 - 真正发请求)              │    │

│  │                                               │    │

│  │   ┌─────────────────────────────────────┐     │    │

│  │   │  DefaultHttpClientAdapter           │     │    │

│  │   │  (基于 dart:io 的 HttpClient)        │     │    │

│  │   └─────────────────────────────────────┘     │    │

│  └──────────────────────────────────────────────┘    │

└──────────────────────────────────────────────────────┘

四大核心组件

| 组件 | 职责 | 类比 |

|------|------|------|

| Options | 存储请求配置(baseUrl、超时、headers等) | 快递单 |

| Interceptors | 请求/响应/错误的拦截处理 | 安检站 |

| Transformer | 请求/响应数据格式转换 | 翻译官 |

| HttpClientAdapter | 真正执行 HTTP 请求的底层实现 | 快递员 |

1.3 底层原理

1.3.1 适配器模式(Adapter Pattern)

Dio 的核心设计哲学是适配器模式。它把"构建请求"和"执行请求"两件事完全分离:


/// Dio 内部的适配器接口

abstract class HttpClientAdapter {

  Future<ResponseBody> fetch(

    RequestOptions options,

    Stream<Uint8List>? requestStream,

    Future<void>? cancelFuture,

  );

}

**为什么要这么设计? **

因为 Flutter 在不同平台(移动端、Web端)底层 HTTP 实现不同:

  • 移动端/桌面端:使用 dart:ioHttpClient
  • Web 端:使用 dart:htmlHttpRequest(XMLHttpRequest)

通过适配器模式,上层代码不需要关心底层用的是什么,只需要换一个 Adapter 就行。


你的代码 → Dio API[适配器接口] → 具体平台实现

                          │

                    ┌─────┴─────┐

                    ▼           ▼

              dart:io       dart:html

            (移动端/桌面)      (Web)

1.3.2 请求的真正执行过程

DefaultHttpClientAdapter(移动端)为例:


// 简化的源码逻辑

class DefaultHttpClientAdapter implements HttpClientAdapter {

  HttpClient? _defaultHttpClient;  // dart:io 的原生 HttpClient

  


  @override

  Future<ResponseBody> fetch(RequestOptions options, ...) async {

    final httpClient = _configHttpClient(options);

  


    // 1. 打开连接

    final reqFuture = httpClient.openUrl(options.method, options.uri);

  


    // 2. 配置请求头

    late HttpClientRequest request;

    request = await reqFuture;

    options.headers.forEach((k, v) => request.headers.set(k, v));

  


    // 3. 写入请求体

    if (requestStream != null) {

      await request.addStream(requestStream);

    }

  


    // 4. 发送请求,获取响应

    final response = await request.close();

  


    // 5. 包装成 ResponseBody 返回

    return ResponseBody(

      response.cast<Uint8List>(),

      response.statusCode,

      headers: headers,

    );

  }

}

执行流程拆解


dart:io HttpClient

    │

    ▼

httpClient.openUrl()    ──→  建立 TCP 连接(Socket)

    │                        ↓

    ▼                     DNS 解析 → IP

request.headers.set()   ──→  构建 HTTP 报文头

    │

    ▼

request.addStream()     ──→  写入请求体(Body)

    │

    ▼

request.close()         ──→  发送请求,等待响应

    │

    ▼

response                ──→  读取响应流(Stream<Uint8List>)

本质上:Dio 的每一次请求,最终都是通过操作系统的 **Socket(套接字) ** 建立 TCP 连接,按照 HTTP 协议格式发送和接收字节流。

1.4 拦截器机制

拦截器是 Dio 最强大的特性之一,采用****责任链模式(Chain of Responsibility)** **。

原理图解


请求发出

  │

  ▼

┌─────────────────┐

│ Interceptor 1   │──→ onRequest (可修改/拦截请求)

│   (如: Token)    │

└────────┬────────┘

         │

         ▼

┌─────────────────┐

│ Interceptor 2   │──→ onRequest

│  (如: 日志)      │

└────────┬────────┘

         │

         ▼

   [发送 HTTP 请求]

         │

         ▼ (响应回来)

┌─────────────────┐

│ Interceptor 2   │──→ onResponse (可修改响应)

│  (如: 日志)      │

└────────┬────────┘

         │

         ▼

┌─────────────────┐

│ Interceptor 1   │──→ onResponse

│   (如: Token)    │

└────────┬────────┘

         │

         ▼

    最终响应返回

类比:想象你寄快递。快递经过多个中转站(拦截器),每个站都可以:

  • 检查/修改 快递内容(onRequest)
  • 拦截退回 这个快递(handler.reject)
  • 直接签收 不再往下传(handler.resolve)
  • 继续传递 给下一个站(handler.next)

拦截器源码核心逻辑


// 简化的拦截器链执行逻辑

Future<Response> _dispatchRequest(RequestOptions options) async {

  // 1. 构建拦截器队列

  final interceptors = [

    ...dio.interceptors,           // 用户添加的拦截器

    _DefaultInterceptor(adapter),  // 最后一个:真正发请求的拦截器

  ];

  


  // 2. 链式调用 - 请求阶段(正序)

  for (final interceptor in interceptors) {

    await interceptor.onRequest(options, handler);

    // handler 内部决定是 next(继续)还是 resolve/reject(中断)

  }

  


  // 3. 链式调用 - 响应阶段(逆序)

  for (final interceptor in interceptors.reversed) {

    await interceptor.onResponse(response, handler);

  }

  


  return response;

}

实际应用示例


class AuthInterceptor extends Interceptor {

  @override

  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {

    // 在每个请求头里自动加上 Token

    final token = StorageService.getToken();

    if (token != null) {

      options.headers['Authorization'] = 'Bearer $token';

    }

    handler.next(options); // 继续传递给下一个拦截器

  }

  


  @override

  void onError(DioException err, ErrorInterceptorHandler handler) async {

    if (err.response?.statusCode == 401) {

      // Token 过期,自动刷新

      final newToken = await refreshToken();

      // 用新 Token 重试原请求

      err.requestOptions.headers['Authorization'] = 'Bearer $newToken';

      final response = await dio.fetch(err.requestOptions);

      handler.resolve(response); // 用新响应替代错误

    } else {

      handler.next(err); // 其他错误继续传递

    }

  }

}

1.5 请求流程全链路

一次完整的 dio.get('/user') 经历以下步骤:


dio.get('/user')

      │

      ▼

  ① 合并配置 (Options)

  │  - baseUrl + path → 完整 URL

  │  - 合并 headers、queryParams、timeout 等

      │

      ▼

  ② 进入拦截器链 - onRequest 阶段

  │  - Interceptor1.onRequest → next

  │  - Interceptor2.onRequest → next

  │  - ...

      │

      ▼

  ③ Transformer 转换请求数据

  │  - 将 Map/Object 转为 JSON 字符串

  │  - 设置 Content-Type

      │

      ▼

  ④ HttpClientAdapter.fetch()

  │  - 建立 TCP 连接

  │  - 发送 HTTP 请求报文

  │  - 接收响应字节流

      │

      ▼

  ⑤ Transformer 转换响应数据

  │  - JSON 字符串 → Map/List

  │  - (在 Isolate 中执行,避免阻塞 UI)

      │

      ▼

  ⑥ 进入拦截器链 - onResponse 阶段

  │  - Interceptor2.onResponse → next

  │  - Interceptor1.onResponse → next

      │

      ▼

  ⑦ 返回 Response<T> 给调用者

1.6 高级特性原理

请求取消(CancelToken)


// 原理:通过 Completer 实现

class CancelToken {

  final Completer<DioException> _completer = Completer();

  


  void cancel([String? reason]) {

    _completer.complete(DioException(...));

  }

  


  // 适配器中监听这个 Future

  // 一旦 cancel 被调用,Future 完成,中断正在进行的请求

}


用户调用 cancel()

       │

       ▼

 Completer.complete()

       │

       ▼

 适配器内的 cancelFuture 触发

       │

       ▼

 httpClient 连接被关闭

       │

       ▼

 抛出 DioException (type: cancel)

类比:CancelToken 就像一个"遥控器",你把遥控器交给正在送快递的快递员,随时可以按下"取消配送"按钮。

文件上传(FormData + MultipartFile)


// FormData 底层会将数据编码为 multipart/form-data 格式

final formData = FormData.fromMap({

  'name': 'dio',

  'file': await MultipartFile.fromFile('./image.png'),

});

  


// 编码后的 HTTP 报文体大致如下:

// --boundary123

// Content-Disposition: form-data; name="name"

//

// dio

// --boundary123

// Content-Disposition: form-data; name="file"; filename="image.png"

// Content-Type: image/png

//

// [二进制文件数据...]

// --boundary123--

JSON 解析在 Isolate 中执行


// Dio 默认的 Transformer 实现

class BackgroundTransformer extends Transformer {

  @override

  Future transformResponse(RequestOptions options, ResponseBody response) async {

    final jsonString = await readResponseAsString(response);

  


    // 数据量大时,在单独的 Isolate 中解析

    // 避免 JSON 解析阻塞 UI 线程

    if (jsonString.length > _kTransformThreshold) {

      return compute(jsonDecode, jsonString); // 在 Isolate 中执行

    }

    return jsonDecode(jsonString);

  }

}

**为什么要在 Isolate 中解析? **

Dart 是单线程模型(Event Loop),如果 JSON 数据很大(比如几 MB),jsonDecode 可能耗时几十毫秒甚至更久,会造成 UI 卡顿。Isolate 是 Dart 的"轻量级线程",可以在另一个线程中执行计算密集型任务。


二、GetX 深入解析

2.1 GetX 是什么

GetX 是 Flutter 的一个超轻量级、全能型框架,集成了三大核心功能:

  1. 状态管理(State Management)

  2. 路由管理(Route Management)

  3. 依赖注入(Dependency Injection)

一句话理解:GetX 就像一个"瑞士军刀",把 Flutter 开发中最常用的三把工具整合到了一起,而且每一把都尽可能地简单好用。

2.2 三大核心模块


┌─────────────────────────────────────────────────┐

│                    GetX                          │

│                                                  │

│  ┌─────────────┐ ┌──────────┐ ┌──────────────┐  │

│  │  状态管理    │ │ 路由管理  │ │   依赖注入   │  │

│  │             │ │          │ │              │  │

│  │ .obs        │ │ Get.to() │ │ Get.put()    │  │

│  │ Obx()      │ │ 中间件    │ │ Get.find()   │  │

│  │ GetBuilder  │ │ 参数传递  │ │ Get.lazyPut()│  │

│  └─────────────┘ └──────────┘ └──────────────┘  │

│                                                  │

│  底层支撑:                                       │

│  ┌──────────────────────────────────────────┐    │

│  │  GetStream / RxNotifier / SmartManagement │    │

│  └──────────────────────────────────────────┘    │

└─────────────────────────────────────────────────┘

2.3 响应式状态管理原理

GetX 的响应式状态管理是其最核心的特性,原理可以概括为:观察者模式 + 自动依赖收集

2.3.1 .obs 的本质

当你写 var count = 0.obs; 时,到底发生了什么?


// .obs 是 Dart 扩展方法,本质上是包装成 Rx<T> 对象

extension IntExtension on int {

  RxInt get obs => RxInt(this);

}

  


// RxInt 继承自 Rx<int>,Rx<int> 继承自 RxNotifier<int>

class RxInt extends Rx<int> {

  RxInt(super.initial);

}

  


// Rx<T> 核心:

class Rx<T> extends RxNotifier<T> {

  T _value;

  


  // 重写 getter —— 读取值时"注册监听"

  @override

  T get value {

    // 🔑 关键!告诉当前正在监听的 Widget:"我被读取了,记住我"

    RxInterface.proxy?.addListener(subject);

    return _value;

  }

  


  // 重写 setter —— 修改值时"通知更新"

  @override

  set value(T val) {

    if (_value != val) {

      _value = val;

      subject.add(val); // 🔑 通知所有监听者

    }

  }

}

类比.obs 把一个普通变量变成了一个"会说话的变量"——

  • 当有人读取它时,它会记住是谁读了它(注册监听者)
  • 当它的值改变时,它会主动喊一声"我变了!"(通知所有监听者)

2.3.2 Obx() 的自动依赖收集

这是 GetX 最巧妙的设计:不需要手动声明依赖,Obx 自动知道该监听哪些变量


// 简化的 Obx 实现原理

class Obx extends StatefulWidget {

  final Widget Function() builder;

  


  @override

  _ObxState createState() => _ObxState();

}

  


class _ObxState extends State<Obx> {

  final _observer = RxNotifier(); // 内部观察者

  late StreamSubscription _subscription;

  


  @override

  void initState() {

    super.initState();

  


    // 订阅:当 _observer 收到通知时,重建 Widget

    _subscription = _observer.listen((_) {

      if (mounted) setState(() {});

    });

  }

  


  @override

  Widget build(BuildContext context) {

    // 🔑 关键三步:

    // 1. 将 _observer 设为全局代理

    RxInterface.proxy = _observer;

  


    // 2. 执行 builder(用户的构建函数)

    //    在执行过程中,所有被读取的 .obs 变量都会

    //    通过 getter 中的 addListener 注册到 _observer

    final result = widget.builder();

  


    // 3. 清除全局代理

    RxInterface.proxy = null;

  


    return result;

  }

}

依赖收集流程图


Obx(() => Text('$count'))  执行过程:

    │

    ▼

① 设置全局代理 proxy = _observer

    │

    ▼

② 执行 builder 函数

    │

    ├──→ 访问 count.value

    │         │

    │         ▼

    │    ③ count 的 getter 被触发

    │         │

    │         ▼

    │    ④ 发现 proxy 不为空

    │    ⑤ 将自己的 Stream 注册到 proxy(_observer)

    │

    ▼

⑥ 清除全局代理 proxy = null

    │

    ▼

⑦ 返回 Text Widget

    │

    ▼

之后 count.value 改变时:

    │

    ▼

⑧ count 的 setter 触发 → subject.add(newValue)

    │

    ▼

⑨ _observer 收到通知 → setState() → Widget 重建

核心原理总结:利用 Dart 的 getter 拦截属性读取,在 build 阶段自动记录"谁读了哪些变量",然后变量变化时精确通知对应的 Widget。

2.3.3 GetBuilder(简单状态管理)

Obx 不同,GetBuilder 使用更传统的方式——手动调用 update()


class CountController extends GetxController {

  int count = 0; // 普通变量,非 .obs

  


  void increment() {

    count++;

    update(); // 手动通知 UI 更新

  }

}

  


// UI 中

GetBuilder<CountController>(

  builder: (controller) => Text('${controller.count}'),

)

GetBuilder 原理


// 简化原理

class GetBuilder<T extends GetxController> extends StatefulWidget {

  @override

  _GetBuilderState createState() => _GetBuilderState();

}

  


class _GetBuilderState extends State<GetBuilder> {

  @override

  void initState() {

    super.initState();

    // 将自己注册到 Controller 的监听者列表

    controller.addListener(_update);

  }

  


  void _update() {

    if (mounted) setState(() {}); // 收到通知就重建

  }

}

  


// GetxController 内部

class GetxController {

  final List<VoidCallback> _listeners = [];

  


  void update() {

    // 通知所有注册的 GetBuilder 重建

    for (final listener in _listeners) {

      listener();

    }

  }

}

Obx vs GetBuilder 对比

  • Obx = 自动挡(自动依赖收集,变量变了自动更新)
  • GetBuilder = 手动挡(手动调 update(),内存占用更小)

2.4 依赖注入原理

GetX 的依赖注入本质上是一个全局的 HashMap 容器


// 简化的核心原理

class GetInstance {

  // 🔑 核心:一个全局 Map,key 是类型+tag,value 是实例信息

  static final Map<String, _InstanceInfo> _instances = {};

  


  // 注册实例

  void put<T>(T instance, {String? tag}) {

    final key = _getKey(T, tag);

    _instances[key] = _InstanceInfo(

      instance: instance,

      isPermanent: false,

    );

  }

  


  // 查找实例

  T find<T>({String? tag}) {

    final key = _getKey(T, tag);

    final info = _instances[key];

    if (info != null) return info.instance as T;

    throw 'Instance not found';

  }

  


  // 延迟注册(首次使用时才创建)

  void lazyPut<T>(T Function() builder, {String? tag}) {

    final key = _getKey(T, tag);

    _instances[key] = _InstanceInfo(

      builder: builder, // 存的是工厂函数,不是实例

      isLazy: true,

    );

  }

  


  // 生成唯一 key

  String _getKey(Type type, String? tag) {

    return tag != null ? '${type.toString()}$tag' : type.toString();

  }

}

依赖注入流程


Get.put(MyController())

        │

        ▼

  生成 key = "MyController"

        │

        ▼

  存入全局 Map: {"MyController": instance}

        │

        ▼

  任何地方调用 Get.find<MyController>()

        │

        ▼

  从 Map 中取出: _instances["MyController"]

        │

        ▼

  返回 MyController 实例 ✅

三种注册方式对比


┌──────────────┬─────────────┬──────────────────────────────────┐

│    方式       │    时机     │          内存行为                 │

├──────────────┼─────────────┼──────────────────────────────────┤

│ Get.put()    │ 立即创建     │ 调用时就创建实例                   │

│ Get.lazyPut()│ 首次使用创建  │ find() 时才创建,节省内存         │

│ Get.putAsync │ 异步创建     │ 用于需要 async 初始化的依赖        │

└──────────────┴─────────────┴──────────────────────────────────┘

2.5 路由管理原理

GetX 的路由管理通过覆盖 Flutter 的 Navigator 实现,使用自定义的 NavigatorObserver 来管理路由栈。


// 简化原理

class GetMaterialApp extends StatelessWidget {

  @override

  Widget build(BuildContext context) {

    return MaterialApp(

      // 🔑 使用自定义的 Navigator key

      navigatorKey: Get.key,

      // 🔑 注入路由观察者

      navigatorObservers: [GetObserver()],

    );

  }

}

  


// Get.to() 的本质

class GetNavigation {

  static final GlobalKey<NavigatorState> key = GlobalKey();

  


  Future<T?> to<T>(Widget page) {

    return key.currentState!.push<T>(

      GetPageRoute(page: () => page),

    );

  }

  


  void back<T>({T? result}) {

    key.currentState!.pop<T>(result);

  }

}

GetX 路由的独特之处:不需要 context


传统 Flutter 路由:

  Navigator.of(context).push(...)  ←── 需要 context

  


GetX 路由:

  Get.to(NextPage())  ←── 不需要 context

  │

  └── 因为 GetX 持有全局 NavigatorKey

      可以直接操作 Navigator

路由中间件


class AuthMiddleware extends GetMiddleware {

  @override

  RouteSettings? redirect(String? route) {

    // 未登录时重定向到登录页

    if (!AuthService.isLoggedIn) {

      return const RouteSettings(name: '/login');

    }

    return null; // null 表示不重定向

  }

  


  @override

  int? get priority => 1; // 优先级,数字越小越先执行

}

2.6 GetX 的生命周期管理

GetX 通过 SmartManagement 自动管理 Controller 的创建和销毁:


┌─────────────────────────────────────────────────────────┐

│                  SmartManagement                         │

│                                                         │

│  ┌───────────────┐  ┌──────────────┐  ┌──────────────┐ │

│  │     full      │  │  onlyBuilder │  │ keepFactory  │ │

│  │  (默认模式)    │  │              │  │              │ │

│  │               │  │              │  │              │ │

│  │ 路由退出时     │  │ 只在使用      │  │ 保留工厂      │ │

│  │ 自动销毁      │  │ GetBuilder时  │  │ 方法不销毁    │ │

│  │ 未使用的      │  │ 自动管理      │  │              │ │

│  │ Controller    │  │              │  │              │ │

│  └───────────────┘  └──────────────┘  └──────────────┘ │

└─────────────────────────────────────────────────────────┘

生命周期回调


class MyController extends GetxController {

  @override

  void onInit() {

    super.onInit();

    // 初始化(类似 initState)

    // 此时 Controller 已创建,可以安全使用

  }

  


  @override

  void onReady() {

    super.onReady();

    // 在第一帧渲染后调用

    // 适合做网络请求、动画启动等

  }

  


  @override

  void onClose() {

    super.onClose();

    // 销毁前调用(类似 dispose)

    // 释放资源:关闭 Stream、Timer 等

  }

}

自动销毁流程


用户按返回键 / Get.back()

        │

        ▼

GetObserver 检测到路由 pop

        │

        ▼

检查该路由关联的 Controller

        │

        ▼

Controller 引用计数 - 1

        │

        ▼

引用计数 == 0 ?

  ├── 是 → 调用 onClose() → 从 Map 移除 → 内存释放 ✅

  └── 否 → 继续保留(还有其他页面在用)


三、Flutter 状态管理方案对比

3.1 setState

最原始的状态管理方式,Flutter 内置。


class CounterPage extends StatefulWidget {

  @override

  _CounterPageState createState() => _CounterPageState();

}

  


class _CounterPageState extends State<CounterPage> {

  int count = 0;

  


  @override

  Widget build(BuildContext context) {

    return TextButton(

      onPressed: () => setState(() => count++),

      child: Text('$count'),

    );

  }

}

原理


setState() 调用

      │

      ▼

标记当前 Element 为 "dirty"(脏)

      │

      ▼

调度一次新的帧(SchedulerBinding.scheduleFrame)

      │

      ▼

下一帧到来时,Flutter 引擎执行 buildScope()

      │

      ▼

遍历所有 dirty Element,调用其 build() 方法

      │

      ▼

生成新的 Widget 树 → Diff 对比 → 更新 RenderObject → 重绘

缺点

  • 状态无法跨组件共享
  • 整个 Widget 子树都会重建(粒度太粗)
  • 业务逻辑和 UI 耦合在一起

3.2 InheritedWidget / Provider

InheritedWidget(Flutter 内置)

原理:利用 Widget 树的上下文传播数据,子孙组件可以 O(1) 时间获取祖先 InheritedWidget 的数据。


        MyApp

          │

    InheritedWidget (持有共享数据)

      ┌───┼───┐

      │   │   │

    子A  子B  子C

               │

             孙D (通过 context 直接访问 InheritedWidget 的数据)


// 核心原理:Element 内部维护了一个 Map

class Element {

  // key: InheritedWidget 的类型

  // value: 对应的 InheritedElement

  Map<Type, InheritedElement>? _inheritedWidgets;

  


  // of(context) 的本质

  T? dependOnInheritedWidgetOfExactType<T>() {

    final element = _inheritedWidgets?[T]; // O(1) 查找

    if (element != null) {

      // 注册依赖关系

      element._dependents.add(this);

    }

    return element?.widget as T?;

  }

}

Provider(对 InheritedWidget 的优雅封装)


Provider 本质 = InheritedWidget + ChangeNotifier + 便捷API


// Provider 使用

ChangeNotifierProvider(

  create: (_) => CounterModel(),

  child: Consumer<CounterModel>(

    builder: (context, model, child) => Text('${model.count}'),

  ),

)

原理


ChangeNotifierProvider

        │

        ▼

创建 InheritedWidget,持有 ChangeNotifier 实例

        │

        ▼

Consumer 通过 context.watch<T>() 获取数据

        │

        ▼

内部调用 dependOnInheritedWidgetOfExactType<T>()

        │

        ▼

注册依赖 → ChangeNotifier.notifyListeners() 时

        │

        ▼

InheritedElement.notifyClients() → Consumer 重建

3.3 Riverpod

Provider 作者的"改良版",完全脱离 Widget 树


// 定义(全局声明,编译时安全)

final counterProvider = StateNotifierProvider<CounterNotifier, int>((ref) {

  return CounterNotifier();

});

  


class CounterNotifier extends StateNotifier<int> {

  CounterNotifier() : super(0);

  void increment() => state++;

}

  


// 使用

class CounterPage extends ConsumerWidget {

  @override

  Widget build(BuildContext context, WidgetRef ref) {

    final count = ref.watch(counterProvider);

    return Text('$count');

  }

}

原理


┌──────────────────────────────────────────┐

│           ProviderContainer              │

│  (全局容器,不依赖 Widget 树)             │

│                                          │

│  ┌──────────────────────────────────┐    │

│  │  Provider Graph (有向无环图 DAG)  │    │

│  │                                  │    │

│  │   providerA ──→ providerB       │    │

│  │       │                          │    │

│  │       ▼                          │    │

│  │   providerC                      │    │

│  └──────────────────────────────────┘    │

└──────────────────────────────────────────┘

  


ref.watch(provider) 的流程:

      │

      ▼

在容器中查找/创建 provider 的状态

      │

      ▼

注册 Widget 为该 provider 的监听者

      │

      ▼

状态变化时 → 通知 Widget 重建

与 Provider 的核心区别

  • Provider 依赖 Widget 树(InheritedWidget),Riverpod 不依赖
  • Riverpod 是编译时安全的(不会出现运行时找不到 Provider 的错误)
  • Riverpod 支持 provider 之间的依赖关系(DAG 图)

3.4 BLoC / Cubit

BLoC = Business Logic Component,基于****流(Stream)** ** 的状态管理。

Cubit(简化版 BLoC)


class CounterCubit extends Cubit<int> {

  CounterCubit() : super(0);

  void increment() => emit(state + 1);

}

  


// UI

BlocBuilder<CounterCubit, int>(

  builder: (context, count) => Text('$count'),

)

BLoC(完整版,Event 驱动)


// 定义事件

abstract class CounterEvent {}

class Increment extends CounterEvent {}

class Decrement extends CounterEvent {}

  


// BLoC

class CounterBloc extends Bloc<CounterEvent, int> {

  CounterBloc() : super(0) {

    on<Increment>((event, emit) => emit(state + 1));

    on<Decrement>((event, emit) => emit(state - 1));

  }

}

原理


┌─────────────────────────────────────────────────────────┐

│                     BLoC 原理                            │

│                                                         │

│   UI 事件                                               │

│     │                                                   │

│     ▼                                                   │

│  ┌──────────┐     ┌───────────────────┐     ┌────────┐ │

│  │  Event   │────→│   BLoC / Cubit    │────→│ State  │ │

│  │ (Stream) │     │                   │     │(Stream)│ │

│  │          │     │  Event → 业务逻辑  │     │        │ │

│  │ 输入流    │     │  → 新 State       │     │ 输出流  │ │

│  └──────────┘     └───────────────────┘     └───┬────┘ │

│                                                  │      │

│                                           StreamBuilder  │

│                                              / BlocBuilder│

│                                                  │      │

│                                                  ▼      │

│                                              UI 更新    │

└─────────────────────────────────────────────────────────┘


// 简化的 Bloc 核心源码

abstract class Bloc<Event, State> extends BlocBase<State> {

  final _eventController = StreamController<Event>.broadcast();

  


  void add(Event event) {

    _eventController.add(event); // 事件进入输入流

  }

  


  void on<E extends Event>(EventHandler<E, State> handler) {

    // 监听特定类型的事件

    _eventController.stream

      .where((event) => event is E)

      .asyncExpand((event) {

        // 执行 handler,handler 中调用 emit() 产生新状态

        return handler(event as E, emit);

      })

      .listen((_) {});

  }

}

  


abstract class BlocBase<State> {

  final _stateController = StreamController<State>.broadcast();

  State _state;

  


  void emit(State state) {

    if (_state == state) return; // 状态没变就不通知

    _state = state;

    _stateController.add(state); // 新状态进入输出流

  }

  


  Stream<State> get stream => _stateController.stream;

}

核心思想

  • 单向数据流:Event → BLoC → State → UI
  • UI 只能通过发送 Event 来触发状态变化
  • BLoC 只能通过 emit 来输出新状态
  • 清晰的职责分离:UI 不知道业务逻辑,BLoC 不知道 UI

3.5 GetX

(详见第二章的深入分析)

核心原理总结:

  • 响应式(Obx):getter/setter 拦截 + 自动依赖收集

  • 简单式(GetBuilder):手动 update() + 监听者列表

3.6 MobX

受 JavaScript MobX 启发,基于代码生成的响应式方案


// 需要 build_runner 生成代码

part 'counter.g.dart';

  


class Counter = _Counter with _$Counter;

  


abstract class _Counter with Store {

  @observable

  int count = 0;

  


  @action

  void increment() => count++;

  


  @computed

  bool get isEven => count % 2 == 0;

}

  


// UI

Observer(

  builder: (_) => Text('${counter.count}'),

)

原理


MobX 核心概念:

  


  Observable (可观察的状态)

       │

       │  被读取时自动追踪

       ▼

  Reaction / Observer (响应/观察者)

       │

       │  状态变化时自动执行

       ▼

  Action (动作)

       │

       │  修改状态的唯一途径

       ▼

  回到 Observable

  


代码生成的作用:

  @observable int count = 0;

       │

  build_runner 生成 ↓

       │

  int get count {

    _$counterAtom.reportRead();  // 追踪读取

    return _count;

  }

  set count(int value) {

    _$counterAtom.reportWrite(value, _count, () {  // 报告写入

      _count = value;

    });

  }

与 GetX 的对比

  • MobX 通过代码生成实现 getter/setter 拦截
  • GetX 通过 Dart 扩展方法(.obs)和 Rx 包装类实现
  • 效果类似,但 GetX 不需要 code generation

3.7 综合对比表

| 维度 | setState | Provider | Riverpod | BLoC | GetX | MobX |

|------|----------|----------|----------|------|------|------|

| 学习成本 | ⭐ | ⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐ |

| 代码量 | 极少 | 少 | 中等 | 多 | 极少 | 中等 |

| 可测试性 | ❌ 差 | ✅ 好 | ✅ 很好 | ✅ 极好 | ⚠️ 一般 | ✅ 好 |

| 可维护性 | ❌ 差 | ✅ 好 | ✅ 很好 | ✅ 极好 | ⚠️ 一般 | ✅ 好 |

| 性能 | ⚠️ 粗粒度 | ✅ 好 | ✅ 好 | ✅ 好 | ✅ 精确更新 | ✅ 精确更新 |

| 需要 context | ✅ 是 | ✅ 是 | ❌ 否 | ✅ 是 | ❌ 否 | ❌ 否 |

| 代码生成 | 否 | 否 | 否 | 否 | 否 | ✅ 需要 |

| 依赖注入 | 无 | 内置 | 内置 | 额外包 | 内置 | 无 |

| 路由管理 | 无 | 无 | 无 | 无 | 内置 | 无 |

| 官方推荐 | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |

| 适用规模 | 小型 | 中小型 | 中大型 | 大型 | 中小型 | 中型 |

3.8 如何选择


项目规模/需求                         推荐方案

    

    ├── 学习/Demo/简单页面             setState

    

    ├── 小型项目,快速开发             GetX

         └── 优点:上手快,一站式解决

    

    ├── 中型项目,团队协作             Provider / Riverpod

         └── Provider: 简单够用

         └── Riverpod: 更安全,更灵活

    

    ├── 大型项目,严格架构             BLoC

         └── 优点:强制单向数据流,可测试性最强

    

    └── 喜欢响应式编程,有 MobX 经验   MobX

各方案核心原理一句话总结

| 方案 | 一句话原理 |

|------|-----------|

| setState | 标记 Element 为脏,下一帧重建整个子树 |

| Provider | InheritedWidget 的语法糖 + ChangeNotifier 通知机制 |

| Riverpod | 全局容器 + Provider 依赖图 + ref.watch 监听变化 |

| BLoC | Stream 双流模型:Event 输入流 → 业务处理 → State 输出流 |

| GetX | Rx 包装 + getter 自动依赖收集 + 全局 Map 依赖注入 |

| MobX | 代码生成 + Observable/Observer 模式 + 自动追踪依赖 |


最后的建议:没有"最好的"状态管理方案,只有"最适合的"。选择时考虑:团队经验、项目规模、维护成本、测试需求。对于大多数项目,Provider/Riverpod 是安全稳妥的选择;追求开发效率选 GetX;追求架构严谨选 BLoC