当前位置: 首页 > news >正文

Flutter Provider 模式实现:基于 InheritedWidget 的状态管理实现

本博客的参考文章:7.3 跨组件状态共享 | 《Flutter实战·第二版》

通过前一篇文章: Flutter InheritedWidget 详解:从生命周期到数据流动的完整解析,我们已经深入理解了 InheritedWidget 的工作原理,现在让我们基于这些知识来实现一个完整的 Provider 模式。这将帮助我们解决实际开发中的状态管理问题。

现有方案的问题

在前面的例子中,我们发现了一个性能问题:每次调用 setState() 时,整个组件树都会重建,即使某些子组件并不依赖变化的数据。

// 问题代码示例
class _InheritedWidgetTestRouteState extends State<InheritedWidgetTestRoute> {int count = 0;Widget build(BuildContext context) {print("😱 整个页面都在重建!");return ShareDataWidget(data: count,child: Column(children: [TestWidget(),        // 依赖数据,需要更新StaticWidget(),      // 不依赖数据,但也被重建了!AnotherWidget(),     // 不依赖数据,但也被重建了!],),);}
}

解决方案:缓存 + Provider 模式

我们需要实现一个智能的状态管理方案,它应该具备以下特性:

  1. 精确更新:只更新真正依赖数据的组件
  2. 缓存机制:避免不必要的重建
  3. 易于使用:提供简洁的 API
  4. 类型安全:支持泛型,避免类型错误

实现 ChangeNotifier:可观察的数据模型

首先,我们需要一个可以通知变化的数据模型:

// 抽象的变化通知器
abstract class ChangeNotifier {List<VoidCallback> _listeners = [];// 添加监听器void addListener(VoidCallback listener) {_listeners.add(listener);}// 移除监听器void removeListener(VoidCallback listener) {_listeners.remove(listener);}// 通知所有监听器void notifyListeners() {for (final listener in _listeners) {listener();}}// 销毁时清理监听器void dispose() {_listeners.clear();}
}// 具体的计数器模型
class CounterModel extends ChangeNotifier {int _count = 0;int get count => _count;void increment() {_count++;print("📊 CounterModel: count 增加到 $_count,通知监听器");notifyListeners(); // 通知所有监听器数据已变化}void decrement() {_count--;print("📊 CounterModel: count 减少到 $_count,通知监听器");notifyListeners();}
}

实现 ChangeNotifierProvider:智能的数据提供者

class ChangeNotifierProvider<T extends ChangeNotifier> extends StatefulWidget {final T Function() create;  // 创建数据模型的工厂函数final Widget child;final bool lazy;           // 是否懒加载const ChangeNotifierProvider({Key? key,required this.create,required this.child,this.lazy = true,}) : super(key: key);// 便捷方法:获取数据模型static T of<T extends ChangeNotifier>(BuildContext context, {bool listen = true}) {if (listen) {// 建立依赖关系,当数据变化时会重建return context.dependOnInheritedWidgetOfExactType<_InheritedProvider<T>>()!.model;} else {// 不建立依赖关系,仅获取数据return context.getElementForInheritedWidgetOfExactType<_InheritedProvider<T>>()!.widget.model;}}_ChangeNotifierProviderState<T> createState() => _ChangeNotifierProviderState<T>();
}class _ChangeNotifierProviderState<T extends ChangeNotifier> extends State<ChangeNotifierProvider<T>> {T? _model;void initState() {super.initState();print("🏗️ ChangeNotifierProvider initState");if (!widget.lazy) {_createModel();}}void _createModel() {if (_model == null) {print("🔧 创建数据模型 ${T.toString()}");_model = widget.create();_model!.addListener(_onModelChanged);}}void _onModelChanged() {print("📢 数据模型变化,触发 setState");// 当模型数据变化时,重建 InheritedWidgetsetState(() {});}Widget build(BuildContext context) {print("🎨 ChangeNotifierProvider build");if (widget.lazy && _model == null) {_createModel();}return _InheritedProvider<T>(model: _model!,child: widget.child,);}void dispose() {print("🗑️ ChangeNotifierProvider dispose");_model?.removeListener(_onModelChanged);_model?.dispose();super.dispose();}
}// 内部使用的 InheritedWidget
class _InheritedProvider<T extends ChangeNotifier> extends InheritedWidget {final T model;const _InheritedProvider({Key? key,required this.model,required Widget child,}) : super(key: key, child: child);bool updateShouldNotify(_InheritedProvider<T> oldWidget) {// 这里我们总是返回 true,因为 ChangeNotifier 已经确保只有在真正变化时才会触发重建bool shouldNotify = oldWidget.model != model;print("⚖️ _InheritedProvider updateShouldNotify: $shouldNotify");return shouldNotify;}
}

实现 Consumer:智能的数据消费者

class Consumer<T extends ChangeNotifier> extends StatelessWidget {final Widget Function(BuildContext context, T model, Widget? child) builder;final Widget? child; // 不需要重建的子组件,用于性能优化const Consumer({Key? key,required this.builder,this.child,}) : super(key: key);Widget build(BuildContext context) {print("🎯 Consumer<${T.toString()}> build");// 获取数据模型并建立依赖关系final model = ChangeNotifierProvider.of<T>(context, listen: true);return builder(context, model, child);}
}// 不监听变化的 Consumer,用于性能优化
class ConsumerWidget<T extends ChangeNotifier> extends StatelessWidget {final Widget Function(BuildContext context, T model) builder;const ConsumerWidget({Key? key,required this.builder,}) : super(key: key);Widget build(BuildContext context) {print("🎯 ConsumerWidget<${T.toString()}> build (不监听变化)");// 获取数据模型但不建立依赖关系final model = ChangeNotifierProvider.of<T>(context, listen: false);return builder(context, model);}
}

完整的使用示例

现在让我们用新的 Provider 模式重写计数器应用:

class ProviderDemoApp extends StatelessWidget {Widget build(BuildContext context) {return MaterialApp(title: 'Provider 模式演示',home: ChangeNotifierProvider<CounterModel>(create: () => CounterModel(),child: CounterHomePage(),),);}
}class CounterHomePage extends StatelessWidget {Widget build(BuildContext context) {print("🏠 CounterHomePage build - 这个页面不会因为计数变化而重建!");return Scaffold(appBar: AppBar(title: Text('Provider 计数器'),),body: Column(children: [// 显示计数的组件 - 会监听变化CounterDisplay(),// 静态组件 - 不会重建StaticInfoWidget(),// 操作按钮区域CounterControls(),// 另一个显示计数的组件AnotherCounterDisplay(),],),);}
}// 显示计数的组件 - 使用 Consumer 监听变化
class CounterDisplay extends StatelessWidget {Widget build(BuildContext context) {return Consumer<CounterModel>(builder: (context, counter, child) {print("🔢 CounterDisplay 重建,新的计数值: ${counter.count}");return Container(padding: EdgeInsets.all(20),margin: EdgeInsets.all(20),decoration: BoxDecoration(color: Colors.blue.shade100,borderRadius: BorderRadius.circular(10),border: Border.all(color: Colors.blue),),child: Column(children: [Text('当前计数',style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),),SizedBox(height: 10),Text('${counter.count}',style: TextStyle(fontSize: 48, color: Colors.blue),),],),);},);}
}// 静态组件 - 不依赖数据,不会重建
class StaticInfoWidget extends StatelessWidget {Widget build(BuildContext context) {print("ℹ️ StaticInfoWidget build - 我不会因为计数变化而重建");return Container(padding: EdgeInsets.all(15),margin: EdgeInsets.symmetric(horizontal: 20),decoration: BoxDecoration(color: Colors.grey.shade100,borderRadius: BorderRadius.circular(8),),child: Row(children: [Icon(Icons.info, color: Colors.grey),SizedBox(width: 10),Text('这是一个静态组件,不会因为计数变化而重建',style: TextStyle(color: Colors.grey.shade700),),],),);}
}// 操作按钮组件
class CounterControls extends StatelessWidget {Widget build(BuildContext context) {print("🎮 CounterControls build");return Container(padding: EdgeInsets.all(20),child: Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly,children: [// 减少按钮 - 不监听变化,仅获取模型来调用方法ConsumerWidget<CounterModel>(builder: (context, counter) {return ElevatedButton.icon(onPressed: counter.decrement,icon: Icon(Icons.remove),label: Text('减少'),style: ElevatedButton.styleFrom(backgroundColor: Colors.red),);},),// 增加按钮 - 不监听变化,仅获取模型来调用方法ConsumerWidget<CounterModel>(builder: (context, counter) {return ElevatedButton.icon(onPressed: counter.increment,icon: Icon(Icons.add),label: Text('增加'),style: ElevatedButton.styleFrom(backgroundColor: Colors.green),);},),],),);}
}// 另一个显示计数的组件 - 演示多个组件可以同时监听同一个数据
class AnotherCounterDisplay extends StatelessWidget {Widget build(BuildContext context) {return Consumer<CounterModel>(// 使用 child 参数优化性能 - 不变的部分不会重建child: Container(padding: EdgeInsets.all(10),decoration: BoxDecoration(color: Colors.orange.shade100,borderRadius: BorderRadius.circular(5),),child: Text('我也在监听计数变化',style: TextStyle(fontWeight: FontWeight.bold),),),builder: (context, counter, child) {print("🔢 AnotherCounterDisplay 重建,计数值: ${counter.count}");return Container(margin: EdgeInsets.all(20),padding: EdgeInsets.all(15),decoration: BoxDecoration(color: Colors.orange.shade50,borderRadius: BorderRadius.circular(10),border: Border.all(color: Colors.orange),),child: Column(children: [child!, // 这部分不会重建SizedBox(height: 10),Text('计数的平方: ${counter.count * counter.count}',style: TextStyle(fontSize: 20, color: Colors.orange.shade800),),],),);},);}
}

执行流程分析

让我们看看点击"增加"按钮时的完整执行流程:

1. 用户点击按钮

🎮 用户点击"增加"按钮↓
📊 CounterModel.increment() 被调用↓ 
📊 CounterModel: count 增加到 1,通知监听器↓
📢 _onModelChanged() 被调用↓
📢 数据模型变化,触发 setState

2. Provider 重建流程

🎨 ChangeNotifierProvider build↓
⚖️ _InheritedProvider updateShouldNotify: true↓
📢 通知所有依赖的子组件

3. 子组件更新流程

🔢 CounterDisplay didChangeDependencies (如果是首次)↓
🔢 CounterDisplay 重建,新的计数值: 1↓
🔢 AnotherCounterDisplay didChangeDependencies (如果是首次)↓
🔢 AnotherCounterDisplay 重建,计数值: 1

注意StaticInfoWidgetCounterHomePage 不会重建!

性能优化技巧

1. 使用 child 参数避免不必要的重建

Consumer<CounterModel>(// 这部分不会因为数据变化而重建child: ExpensiveWidget(),builder: (context, counter, child) {return Column(children: [Text('计数: ${counter.count}'), // 会重建child!,                        // 不会重建],);},
)

2. 选择性监听

class SmartWidget extends StatelessWidget {Widget build(BuildContext context) {// 只获取数据,不监听变化final counter = ChangeNotifierProvider.of<CounterModel>(context, listen: false);return ElevatedButton(onPressed: counter.increment, // 只是调用方法,不需要监听数据变化child: Text('增加'),);}
}

3. 多个 Provider 的组合使用

class MultiProviderApp extends StatelessWidget {Widget build(BuildContext context) {return MaterialApp(home: ChangeNotifierProvider<CounterModel>(create: () => CounterModel(),child: ChangeNotifierProvider<UserModel>(create: () => UserModel(),child: ChangeNotifierProvider<ThemeModel>(create: () => ThemeModel(),child: MyHomePage(),),),),);}
}// 在子组件中使用多个 Provider
class MultiConsumerWidget extends StatelessWidget {Widget build(BuildContext context) {return Consumer<CounterModel>(builder: (context, counter, _) {return Consumer<UserModel>(builder: (context, user, _) {return Text('${user.name}: ${counter.count}');},);},);}
}

与原生 Provider 包的对比

我们实现的简化版 Provider 具备了核心功能:

功能我们的实现Provider 包
基本状态管理
精确更新
类型安全
性能优化
多 Provider 支持
Selector 优化
ProxyProvider
更多便捷 API

总结

通过实现这个 Provider 模式,我们深入理解了:

  1. InheritedWidget 的实际应用:如何将理论知识转化为实用的状态管理方案
  2. 缓存机制的重要性:避免不必要的组件重建
  3. 观察者模式ChangeNotifier 如何通知数据变化
  4. 性能优化策略:选择性监听、子组件缓存等技巧
http://www.xdnf.cn/news/1308169.html

相关文章:

  • 矩阵链相乘的最少乘法次数(动态规划解法)
  • 开源 Arkts 鸿蒙应用 开发(十七)通讯--http多文件下载
  • bilibili视频总结
  • RK3568 NPU RKNN(一):概念理清
  • 【P14 3-6 】OpenCV Python——视频加载、摄像头调用、视频基本信息获取(宽、高、帧率、总帧数)
  • 10-verilog的EEPROM驱动-单字节读写
  • 罗技MX Anywhere 2S鼠标修复记录
  • 多机编队——(6)解决机器人跟踪过程中mpc控制转圈问题
  • AT89C52单片机介绍
  • CVE-2024-28752漏洞复现
  • mysql一启动就挂的解决
  • Javar如何用RabbitMQ订单超时处理
  • Docker部署 Neo4j Community【拒绝国内镜像拉取异常】
  • Vue组件生命周期钩子:深入理解组件的生命周期阶段
  • 论文学习24:Boundary-Sensitive Segmentation of SmallLiver Lesions
  • 服务器可以ping通,但部署的网站打不开
  • [Linux] Linux tar文档管理 系统间复制文档
  • Android 移动端 UI 设计:前端常用设计原则总结
  • 使用openssl创建自签名CA并用它签发服务器证书
  • c# WebAssembly,在网页上能运行多线程,异步,锁,原子加,减等代码吗
  • tailscale远程服务器连接局域网方案(解决境外服务器网速慢的问题)
  • OBOO鸥柏丨75寸/86平板企业办公会议触控一体机核心国产化品牌招投标参数
  • 企业运维规划及Linux介绍虚拟环境搭建
  • Jenkins Pipeline中参数化构建
  • 5 索引的操作
  • 惠普声卡驱动win10装机完成检测不到声卡
  • 每日任务day0816:小小勇者成长记之符文羊皮卷
  • ML307C 4G通信板:工业级DTU固件,多协议支持,智能配置管理
  • AI热点周报(8.10~8.16):AI界“冰火两重天“,GPT-5陷入热议,DeepSeek R2模型训练受阻?
  • c#Blazor WebAssembly在网页中多线程计算1000万次求余