面向 Flutter/Dart 的响应式与状态管理,easy_rxdart 提供统一的
Stream/Reactive操作与Model + InheritedWidget组合,覆盖防抖/节流/去重、错误恢复、第三方库响应式封装。核心设计旨在减少样板代码、提升组合能力与可读性,让业务逻辑围绕流与状态自然生长,适配中小型到复杂场景的架构演进。
方案选型
- 响应式核心:以
Stream为主干,扩展操作符满足事件流需求;用Reactive<T>统一链式与组合语义。 - 状态管理:
Model基于Listenable,通过EasyModel<T>提供上下文,watch/read/listen精准区分重建与副作用。 - 第三方整合:通过扩展与工具方法,对
dio、权限、图片选择、存储等提供一致的响应式调用。 - 取舍与对比:相较 BLoC,减少事件/状态样板,强调"以流为中心"的组合与直观
Model触发;相较 Riverpod,更贴近 Flutter 机制(InheritedWidget + AnimatedBuilder),简单可控;需要跨层依赖时,用EasyModelManager做全局管理。
架构设计
- 目录分层:
- 核心:
Reactive<T>、流操作扩展、Model与EasyModel<T>、EasyStreamController、Subject包装。 - 扩展:面向
Stream/Reactive/Widget/第三方库的便捷操作。 - 工具:
debounce/throttle/distinct、时间/格式化、定时器组、网络/连接状态。 - Mixin:应用与路由生命周期、订阅管理。
- 核心:
- 模块职责:
Reactive<T>:包装Stream<T>提供map/flatMap/where/combineLatest/zip/concat/listen,兼容 rxdart。Model + EasyModel<T>:版本与微任务去重策略的最小重建;watch/read/listen三分法。Stream扩展:debounceTime/throttleTime/distinctUntilChanged/retryWithDelay/withLatestFrom/buffer/window/sample/audit等。- 管理与集成:
EasyModelManager.lazyPut/get/put/delete/reset全局依赖;第三方能力响应式化。
整体流程图

核心数据流
- 事件输入:来自控件、网络、定时器、第三方库等。
- 操作符链:集中完成过滤、限流、错误恢复与组合。
- 状态触发:
Model.notifyListeners()驱动 UI 最小重建;toReactive将状态投射为流用于组合。 - 副作用订阅:无需重建时,用
listen执行副作用。
flowchart LR
UI[TextField / Gesture] --> S[Stream / Stream] --> O[debounceTime / throttleTime / distinctUntilChanged] --> M[map / flatMap / combine / zip] --> ST[Model 状态 或 Reactive 输出] --> R[rebuild 或 side-effect]
最小可用示例
定义模型
dart
class CounterModel extends Model {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
提供与消费
dart
EasyModel<CounterModel>(
model: CounterModel(),
child: Builder(
builder: (context) {
final model = EasyModel.watch<CounterModel>(context)!;
return Column(
children: [
Text('${model.count}')
,
ElevatedButton(
onPressed: () => EasyModel.read<CounterModel>(context)?.increment(),
child: const Text('Add'),
),
],
);
},
),
);
文本输入搜索流
dart
final input = StreamController<String>.broadcast();
final searchStream = input.stream
.debounceTime(const Duration(milliseconds: 300))
.distinctUntilChanged()
.flatMapValue((q) => fetchResult(q))
.retryWithDelay(count: 3, delay: const Duration(milliseconds: 500));
searchStream.listen((items) {
});
状态到流的桥接
将模型状态投射为 Reactive<T>,用于组合或跨组件订阅。
dart
final counterReactive = model.toReactive(() => model.count);
counterReactive.map((v) => 'Count: $v').listen((text) {
});
第三方集成示例(网络请求)
合理结合错误恢复与重试。
dart
Stream<List<User>> getUsers() =>
Stream.fromFuture(dio.get('/users'))
.map((resp) => parseUsers(resp.data))
.retryWithDelay(count: 2, delay: const Duration(seconds: 1))
.onErrorReturnItem(<User>[]);
关键设计细节
- 重建控制:
Model使用版本与微任务去重策略,避免短时间内重复触发。watch触发构建,read不触发构建,listen用于副作用。 - 订阅生命周期:控制器/Subject 包装统一"谁创建谁销毁";Mixin 自动清理路由/应用生命周期绑定。
- 错误治理:
timeoutTime/retryWithDelay/onErrorReturn/onErrorResumeNext/defaultIfEmpty/materialize/dematerialize。 - 组合能力:
merge/concat/combineLatest/zip/withLatestFrom;窗口与缓冲:windowCount/windowTime/bufferCount/bufferTime。
典型场景落地
- 输入框防抖搜索:
debounceTime + distinctUntilChanged + flatMapValue + retryWithDelay。 - 滑动或点击行为治理:对交互加
debounce/throttle/distinct。 - 从状态驱动 UI:
Model维护最小状态集,EasyModel<T>向下传递,构建边界清晰。 - 复杂流编排:并发/序列/压缩三类组合,对应
merge/concat/zip。
流程图:网络请求装配线
flowchart TD
REQ[请求触发] --> F[Future -> Stream] --> RETRY[retryWithDelay] --> MAP[map / 解析] --> FALLBACK[onErrorReturnItem 或 defaultIfEmpty] --> OUT[输出到 Model / Reactive] --> UI[UI 重建 或 副作用]
性能与工程实践
- 边界清晰:将"重建"与"副作用"拆分,避免过度重建。
- 优先扩展操作符:用扩展而非手工逻辑,减少不可预期状态。
- 错误兜底:所有外部 IO 流建议配置兜底值与重试策略。
- 资源回收:统一关闭控制器与订阅;跨页面订阅用 Mixin 自动清理。
- 可测试性:流管线易单测,模型可通过版本与哈希策略验证通知行为。
与主流方案的协作
- 与 Riverpod 协作:外层管理依赖,内层用
Model + Reactive做流编排与最小重建。 - 与 BLoC 协作:保留既有事件/状态结构时,将副作用和组合逻辑沉到
Stream扩展与Reactive。
适用边界
- 最佳适配:事件主导交互、网络数据装配、轻到中型状态管理、端上能力整合。
- 不适配:跨团队大型复杂域模型、严格 CQRS/DDD 的大规模事件场景,建议与更重型框架配合。
总结与落地建议
- easy_rxdart 将响应式与状态管理统一到可组合的流与轻量模型之上,降低样板与心智负担。
- 建议从"输入防抖 + 网络装配 + 模型驱动"起步,逐步引入窗口/缓冲与生命周期治理,避免一开始过度工程化。
实践清单
- 输入框搜索:
debounceTime + distinctUntilChanged + flatMapValue + retryWithDelay - 列表滚动埋点:
throttleTime + mapNotNull + bufferTime - 登录态与页面联动:
Model.toReactive + combineLatest2 + defaultIfEmpty - 网络兜底:
timeoutTime + onErrorReturnItem + retryWithDelay