目录
- 1.2 核心架构
- 1.3 底层原理
- 3.2 InheritedWidget / Provider
- 3.5 GetX
- 3.6 MobX
- 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:io的HttpClient
- Web 端:使用
dart:html的HttpRequest(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 的一个超轻量级、全能型框架,集成了三大核心功能:
-
状态管理(State Management)
-
路由管理(Route Management)
-
依赖注入(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。