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 模式
我们需要实现一个智能的状态管理方案,它应该具备以下特性:
- 精确更新:只更新真正依赖数据的组件
- 缓存机制:避免不必要的重建
- 易于使用:提供简洁的 API
- 类型安全:支持泛型,避免类型错误
实现 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
注意:StaticInfoWidget
和 CounterHomePage
不会重建!
性能优化技巧
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 模式,我们深入理解了:
- InheritedWidget 的实际应用:如何将理论知识转化为实用的状态管理方案
- 缓存机制的重要性:避免不必要的组件重建
- 观察者模式:
ChangeNotifier
如何通知数据变化 - 性能优化策略:选择性监听、子组件缓存等技巧