📚 第一部分:什么是 Zone?
Zone 的定义
在 Dart 中,Zone 是一种执行上下文(execution context),你可以把它想象成一个"气泡"或"容器",代码在这个容器里执行时,可以访问到一些特定的环境变量和配置。
用更通俗的比喻:
- 🏠 Zone 就像一个"房间"
- 📦 房间里有一些"储物柜"(zoneValues)
- 🚪 进入房间的代码可以访问这些储物柜
- 🔑 每个储物柜有一个键(key),用来存取数据
最简单的 Zone 示例
dart
import 'dart:async';
void main() {
// 普通执行环境
print('外部: ${Zone.current[#userId]}'); // null
// 创建一个 Zone,并在其中存储数据
runZoned(() {
print('Zone 内部: ${Zone.current[#userId]}'); // "user123" ✅
someFunction();
}, zoneValues: {
#userId: 'user123', // 👈 存储数据到 Zone
});
}
void someFunction() {
// 这个函数在 Zone 内部执行,可以读取 Zone 中的数据
final userId = Zone.current[#userId] as String?;
print('someFunction 获取到的 userId: $userId'); // "user123" ✅
}
输出:
yaml
外部: null
Zone 内部: user123
someFunction 获取到的 userId: user123
Zone 的核心特性
1. 数据隔离
每个 Zone 都有自己的数据空间,互不干扰:
dart
runZoned(() {
print('Zone A: ${Zone.current[#name]}'); // "Alice"
}, zoneValues: {#name: 'Alice'});
runZoned(() {
print('Zone B: ${Zone.current[#name]}'); // "Bob"
}, zoneValues: {#name: 'Bob'});
print('外部: ${Zone.current[#name]}'); // null
2. 继承性
Zone 可以嵌套,内层 Zone 可以访问外层 Zone 的数据:
dart
runZoned(() {
print('外层 Zone: ${Zone.current[#outer]}'); // "outer-value"
runZoned(() {
print('内层 Zone - 外层数据: ${Zone.current[#outer]}'); // "outer-value" ✅
print('内层 Zone - 内层数据: ${Zone.current[#inner]}'); // "inner-value" ✅
}, zoneValues: {
#inner: 'inner-value',
});
}, zoneValues: {
#outer: 'outer-value',
});
3. 作用域限制
Zone 中存储的数据只在这个 Zone 的执行范围内有效:
dart
void main() {
runZoned(() {
scheduleMicrotask(() {
// 异步任务仍在同一个 Zone 中 ✅
print('异步任务: ${Zone.current[#data]}'); // "hello"
});
}, zoneValues: {#data: 'hello'});
// 这里已经退出 Zone
print('外部: ${Zone.current[#data]}'); // null
}
🎯 第二部分:Zone 的常见用途
1. 全局错误捕获
Zone 最常见的用途之一是捕获所有未处理的异常:
dart
void main() {
runZonedGuarded(() {
runApp(MyApp());
}, (error, stackTrace) {
// 👇 捕获所有未处理的异常
print('捕获到错误: $error');
reportErrorToServer(error, stackTrace);
});
}
用途:在生产环境中收集所有崩溃信息,发送到错误追踪服务。
2. 自定义 print 输出
可以拦截和修改 print 的行为:
dart
void main() {
runZoned(() {
print('这条日志会被拦截');
print('并添加前缀');
}, zoneSpecification: ZoneSpecification(
print: (self, parent, zone, message) {
// 👇 拦截 print,添加自定义前缀
parent.print(zone, '[MyApp] $message');
},
));
}
输出:
csharp
[MyApp] 这条日志会被拦截
[MyApp] 并添加前缀
3. 异步操作追踪
Zone 可以追踪和管理异步操作:
dart
runZoned(() async {
await Future.delayed(Duration(seconds: 1));
print('异步操作完成');
// 👆 这个异步操作仍在 Zone 内执行
}, zoneValues: {
#requestId: 'req-12345',
});
Flutter 框架内部就是用 Zone 来追踪异步操作的来源,这就是为什么你在 async 方法中抛出的异常能被正确捕获。
4. 上下文传递(最重要!)
这是 view_model 使用 Zone 的核心用途:在不显式传参的情况下,将数据传递给深层的函数调用。
dart
void main() {
runZoned(() {
processRequest();
}, zoneValues: {
#currentUser: User(id: '123', name: 'Alice'),
});
}
void processRequest() {
// 不需要传参,直接从 Zone 中获取
validatePermission();
}
void validatePermission() {
// 深层调用,仍然可以访问 Zone 数据
final user = Zone.current[#currentUser] as User;
print('验证用户权限: ${user.name}');
}
优势:
- ✅ 不需要层层传递参数
- ✅ 保持函数签名简洁
- ✅ 数据在整个调用链中都可访问
🚀 第三部分:view_model 如何借助 Zone 实现依赖机制
问题背景
在 view_model 架构中,我们遇到了一个经典难题:
场景:ViewModel 想在构造函数中获取其他 ViewModel 依赖
dart
class UserProfileViewModel extends ViewModel {
UserProfileViewModel() {
// 🤯 问题:我想在构造函数里获取 AuthViewModel
// 但是依赖解析能力在 State 中,这里根本访问不到!
final authVM = ??? // 从哪里获取?
if (authVM.isLoggedIn) {
loadUserProfile();
}
}
}
核心矛盾:
- ✅ Widget/State 有 BuildContext,可以访问依赖容器
- ❌ ViewModel 构造函数 没有 BuildContext,无法直接获取依赖
- ❌ 传递 BuildContext 到 ViewModel?违背了架构分层原则
我们需要一种机制:
- 不破坏架构分层(ViewModel 不依赖 Widget)
- 不显式传参(保持构造函数简洁)
- 让 ViewModel 能神奇地获取到依赖解析能力
答案就是:Zone!
解决方案:用 Zone 传递依赖解析器
核心思路:在创建 ViewModel 时,用 Zone 包裹构造过程,将依赖解析器存入 Zone。
第一步:定义依赖解析器类型
dart
// dependency_handler.dart
// 依赖解析器的函数签名
typedef DependencyResolver = T Function<T extends ViewModel>({
required ViewModelDependencyConfig<T> dependency,
bool listen,
});
const _resolverKey = #_viewModelDependencyResolver; // Zone 中的键
第二步:创建辅助函数,用 Zone 包裹执行
dart
// dependency_handler.dart
/// 用 Zone 包裹 body 的执行,并将 resolver 存入 Zone
R runWithResolver<R>(R Function() body, DependencyResolver resolver) {
return runZoned(body, zoneValues: {
_resolverKey: resolver, // 👈 将解析器存入 Zone
});
}
第三步:在 ViewModelAttacher 创建 ViewModel 时使用 Zone
dart
// attacher.dart
VM _createViewModel<VM extends ViewModel>({
required ViewModelFactory<VM> factory,
bool listen = true,
}) {
// ...
// 👇 关键!用 runWithResolver 包裹 ViewModel 的创建
final res = runWithResolver(
() {
return _instanceController.getInstance<VM>(
factory: InstanceFactory<VM>(
builder: factory.build, // 👈 这里会调用 ViewModel 的构造函数
// ...
),
)..dependencyHandler.addDependencyResolver(onChildDependencyResolver);
},
onChildDependencyResolver, // 👈 将依赖解析器传入 Zone
);
// ...
return res;
}
这一步发生了什么?
scss
┌────────────────────────────────────────────────────┐
│ 1. 调用 _createViewModel<UserProfileViewModel>() │
│ ↓ │
│ 2. runWithResolver(..., onChildDependencyResolver) │
│ 创建 Zone { _resolverKey: resolver } │
│ ↓ │
│ 【进入 Zone,携带依赖解析器】 │
│ ↓ │
│ 3. factory.build() → UserProfileViewModel() │
│ 构造函数在 Zone 中执行 │
└────────────────────────────────────────────────────┘
第四步:DependencyHandler 从 Zone 中读取解析器
dart
// dependency_handler.dart
class DependencyHandler {
final List<DependencyResolver> dependencyResolvers = [];
T getViewModel<T extends ViewModel>({
Object? key,
Object? tag,
ViewModelFactory<T>? factory,
bool listen = false,
}) {
// 👇 双重保障:先查列表,再查 Zone
final resolver = dependencyResolvers.firstOrNull ??
(Zone.current[_resolverKey] as DependencyResolver?);
if (resolver == null) {
throw StateError('No dependency resolver available');
}
// 使用解析器获取依赖
return resolver(
dependency: ViewModelDependencyConfig<T>(...),
listen: listen,
);
}
}
关键点:
Zone.current[_resolverKey]读取当前 Zone 中的解析器- 双重保障:
- 优先使用
dependencyResolvers列表(已添加的 resolver) - 如果列表为空,从 Zone 中读取
- 优先使用
第五步:ViewModel 在构造函数中愉快地获取依赖!
dart
class UserProfileViewModel extends ViewModel {
late final AuthViewModel _authVM;
UserProfileViewModel() {
// ✅ 现在可以直接调用了!
_authVM = readViewModel<AuthViewModel>();
if (_authVM.isLoggedIn) {
loadUserProfile();
}
}
// readViewModel 的实现
T readViewModel<T extends ViewModel>() {
// 内部调用 dependencyHandler.getViewModel()
// 它会从 Zone 中读取解析器 ✅
return dependencyHandler.getViewModel<T>();
}
}
完整的调用链
scss
┌──────────────────────────────────────────────────────────┐
│ 1. State.watchViewModel<UserProfileViewModel>() │
│ ↓ │
│ 2. attacher._createViewModel() │
│ ↓ │
│ 3. runWithResolver( │
│ () => factory.build(), │
│ onChildDependencyResolver 👈 存入 Zone │
│ ) │
│ ↓ │
│ 【进入 Zone,携带 onChildDependencyResolver】 │
│ ↓ │
│ 4. UserProfileViewModel() 构造函数被调用 │
│ ↓ │
│ 5. readViewModel<AuthViewModel>() │
│ ↓ │
│ 6. dependencyHandler.getViewModel<AuthViewModel>() │
│ ↓ │
│ 7. 从 Zone.current[_resolverKey] 读取 resolver ✅ │
│ ↓ │
│ 8. resolver<AuthViewModel>() │
│ → 调用 State 的 onChildDependencyResolver │
│ → 回到 State 的上下文 │
│ → 创建/获取 AuthViewModel ✅ │
│ ↓ │
│ 9. 返回 AuthViewModel 实例给 UserProfileViewModel │
└──────────────────────────────────────────────────────────┘
为什么添加到 dependencyResolvers 列表?
在创建完 ViewModel 后,会将 resolver 添加到列表中:
dart
final res = runWithResolver(
() {
return _instanceController.getInstance<VM>(...)
..dependencyHandler.addDependencyResolver(onChildDependencyResolver); // 👈
},
onChildDependencyResolver,
);
原因:
- ViewModel 创建时 :在 Zone 中,可以从
Zone.current[_resolverKey]获取 - ViewModel 创建后:Zone 已退出,但 resolver 已添加到列表中
- 后续调用
readViewModel时,从dependencyResolvers列表中获取
双重保障确保 ViewModel 在任何时候都能访问依赖解析器!
🌟 第四部分:Zone 方案的优势
1. 架构纯净
dart
// ❌ 不好的方案:ViewModel 依赖 BuildContext
class UserProfileViewModel extends ViewModel {
UserProfileViewModel(BuildContext context) {
final authVM = context.read<AuthViewModel>(); // 违背分层原则
}
}
// ✅ 优雅的方案:ViewModel 完全独立
class UserProfileViewModel extends ViewModel {
UserProfileViewModel() {
final authVM = readViewModel<AuthViewModel>(); // 不依赖任何 Widget 概念
}
}
优势:
- ViewModel 不知道 Widget、BuildContext 的存在
- 可以在任何环境中测试(不需要 Widget 树)
- 保持了清晰的架构分层
2. 开发体验极佳
dart
class OrderViewModel extends ViewModel {
OrderViewModel() {
// 👇 像写同步代码一样简洁
final userVM = readViewModel<UserViewModel>();
final cartVM = readViewModel<CartViewModel>();
final paymentVM = readViewModel<PaymentViewModel>();
// 直接使用,无需任何样板代码
if (userVM.isLoggedIn && cartVM.hasItems) {
paymentVM.calculateTotal(cartVM.items);
}
}
}
优势:
- 代码简洁,可读性强
- 无需层层传递参数
- 构造函数逻辑清晰
3. Zone 的自动传播
dart
class ViewModel1 extends ViewModel {
ViewModel1() {
// 创建 ViewModel2 时,Zone 仍然有效
final vm2 = readViewModel<ViewModel2>();
}
}
class ViewModel2 extends ViewModel {
ViewModel2() {
// ViewModel2 的构造函数仍在同一个 Zone 中
// 可以继续获取其他依赖
final vm3 = readViewModel<ViewModel3>();
}
}
优势:
- Zone 会自动传播到整个调用链
- 多级依赖的 ViewModel 可以无缝工作
- 不需要额外的传递逻辑
4. 类型安全
dart
// ✅ 编译时类型检查
final authVM = readViewModel<AuthViewModel>(); // AuthViewModel 类型
authVM.login(); // IDE 提供完整的代码补全
// ❌ 如果用 Map 传递参数,失去类型安全
final authVM = dependencies['auth'] as AuthViewModel?; // 运行时可能出错
📦 总结
通过 Zone 机制,view_model 实现了优雅的依赖注入,让开发者可以在 ViewModel 构造函数中自然地获取依赖,同时保持架构的清晰和纯净!🚀 来试试:pub.dev/packages/vi...