Flutter中使用Observer设计模式替代Provider是否是好方案?
结论先行
完全可以不引入provider、riverpod这类第三方依赖,直接基于Dart面向对象特性+Flutter原生能力实现跨Widget数据监听、精准局部重建,本质上绝大多数第三方状态管理库的底层核心逻辑,就是你说的观察者(发布-订阅)模式。
很多人有个误区:觉得ChangeNotifier是provider提供的类,实际上它是Flutter SDK foundation 库内置的基础能力,从首个稳定版就存在,专门用来实现事件监听,不需要安装任何第三方包就能用。
最简原生实现方案
1. 定义可观察的数据模型
直接继承Flutter内置的ChangeNotifier,在数据变更时调用notifyListeners()通知所有订阅者即可:
import 'package:flutter/foundation.dart'; // 无任何第三方依赖,纯SDK原生能力 class CounterModel extends ChangeNotifier { int _count = 0; // 对外暴露只读的变量值 int get count => _count; void increment() { _count++; // 所有监听该模型的对象都会收到变更通知 notifyListeners(); } // 页面/组件销毁时调用,清空监听器避免内存泄漏 @override void dispose() { super.dispose(); } }
2. 实现精准局部重建
用Flutter原生的ListenableBuilder组件监听模型变化,只有builder返回的组件会在数据变更时重建,不会触发父组件、其他无关组件的刷新,性能和第三方状态库没有差异:
// 按业务作用域持有模型实例:全局共享就用单例,页面内共享就放在页面根State中 final counterModel = CounterModel(); // 需要响应数据变化的Widget X class WidgetX extends StatelessWidget { const WidgetX({super.key}); @override Widget build(BuildContext context) { return ListenableBuilder( listenable: counterModel, // 不需要随数据刷新的静态子组件,放在child参数里不会被重建 child: const Text('固定文本:不会随计数变化刷新'), builder: (context, staticChild) { return Column( children: [ // 这部分内容会随数据变更自动更新 Text('当前计数值:${counterModel.count}'), staticChild!, ], ); }, ); } }
3. 触发数据变更
在Widget Y中直接调用模型的更新方法即可,所有监听者会自动收到通知:
// 修改变量的Widget Y class WidgetY extends StatelessWidget { const WidgetY({super.key}); @override Widget build(BuildContext context) { return ElevatedButton( onPressed: () { counterModel.increment(); }, child: const Text('点击加1'), ); } }
如果需要在数据变化时执行除了UI刷新之外的逻辑(比如弹提示、跳转页面、上报埋点),直接在StatefulWidget的生命周期里手动添加监听器即可,记得在组件销毁时移除监听:
class _WidgetXState extends State<WidgetX> { @override void initState() { super.initState(); counterModel.addListener(_handleDataChange); } void _handleDataChange() { // 执行自定义逻辑 if (counterModel.count == 10) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('计数到10啦'))); } } @override void dispose() { // 必须移除监听,否则会内存泄漏 counterModel.removeListener(_handleDataChange); super.dispose(); } // ... build方法省略 }
纯Dart手写发布-订阅实现
如果连Flutter的foundation库都不想依赖,纯用Dart语言特性实现一套发布订阅逻辑也就几十行代码,完全可以满足需求:
// 纯Dart实现的可观察基类,零依赖 class BaseObservable { final List<void Function()> _listeners = []; void addListener(void Function() listener) => _listeners.add(listener); void removeListener(void Function() listener) => _listeners.remove(listener); void notifyListeners() { // 拷贝列表遍历,避免遍历过程中监听器增删导致的并发错误 for (final fn in List.of(_listeners)) { fn(); } } } // 业务模型直接继承即可 class UserModel extends BaseObservable { String _userName = '未登录'; String get userName => _userName; void updateUserName(String newName) { _userName = newName; notifyListeners(); } }
实践注意事项
- 实例作用域要匹配业务:全局共享的数据就用单例持有,单页面/模块共享的数据放在对应根节点的State中,不要在子组件里重复创建模型实例,否则会丢状态。
- 控制刷新范围:尽量把
ListenableBuilder的范围缩小到真正需要更新的组件,静态内容尽量放到child参数中传递,避免不必要的重建。 - 小项目完全够用:我自己做过的多个中小规模Flutter项目,早期都是用这套原生方案做状态管理,没有第三方依赖的版本兼容问题,包体积更小,性能完全满足需求。等项目规模扩大、协作人数变多,需要调试工具、异步状态封装、路由级生命周期管理能力的时候,再换成熟的第三方库也不迟,底层逻辑一致,迁移成本极低。
内容的提问来源于stack exchange,提问作者wajih abidi
相关产品推荐
相关产品推荐

