You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 22:03:29